From 570ded10fa7cc17762363dcf622e72664a6fee15 Mon Sep 17 00:00:00 2001 From: lublak <44057030+lublak@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:51:04 +0100 Subject: [PATCH 001/204] Set cursor to pointer for drawio diagrams Add cursor style for drawio diagrams in TinyMCE. --- resources/sass/_tinymce.scss | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss index 8cc96df4193..561bb23cae0 100644 --- a/resources/sass/_tinymce.scss +++ b/resources/sass/_tinymce.scss @@ -202,4 +202,12 @@ body.page-content.mce-content-body { background-image: url('data:image/svg+xml;utf8,'); background-position: 50% 50%; background-size: 100% 100%; -} \ No newline at end of file +} + +/** + * Set correct cursor for drawio + */ + +.page-content.mce-content-body [drawio-diagram] { + cursor: pointer; +} From 6661ae81782506a4f9dbf54ba8b74a2edc30ced7 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 7 Dec 2025 15:02:06 +0000 Subject: [PATCH 002/204] Lexical: Improved focus control for popup modal forms Now moves focus to first field on open, and restores focus back to editor on submit/close. --- resources/js/wysiwyg/ui/framework/forms.ts | 7 +++++++ resources/js/wysiwyg/ui/framework/modals.ts | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/resources/js/wysiwyg/ui/framework/forms.ts b/resources/js/wysiwyg/ui/framework/forms.ts index b12d9f692fc..3d8c53d46a2 100644 --- a/resources/js/wysiwyg/ui/framework/forms.ts +++ b/resources/js/wysiwyg/ui/framework/forms.ts @@ -98,6 +98,13 @@ export class EditorForm extends EditorContainerUiElement { this.definition = definition; } + focusOnFirst() { + const focusable = this.getDOMElement().querySelector('input,select,textarea'); + if (focusable) { + (focusable as HTMLElement).focus(); + } + } + setValues(values: Record) { for (const name of Object.keys(values)) { const field = this.getFieldByName(name); diff --git a/resources/js/wysiwyg/ui/framework/modals.ts b/resources/js/wysiwyg/ui/framework/modals.ts index 4dbe9d962c5..3f5a5881f21 100644 --- a/resources/js/wysiwyg/ui/framework/modals.ts +++ b/resources/js/wysiwyg/ui/framework/modals.ts @@ -14,6 +14,7 @@ export interface EditorFormModalDefinition extends EditorModalDefinition { export class EditorFormModal extends EditorContainerUiElement { protected definition: EditorFormModalDefinition; protected key: string; + protected originalFocus: Element|null = null; constructor(definition: EditorFormModalDefinition, key: string) { super([new EditorForm(definition.form)]); @@ -22,6 +23,7 @@ export class EditorFormModal extends EditorContainerUiElement { } show(defaultValues: Record) { + this.originalFocus = document.activeElement as Element; const dom = this.getDOMElement(); document.body.append(dom); @@ -31,11 +33,15 @@ export class EditorFormModal extends EditorContainerUiElement { form.setOnSuccessfulSubmit(this.hide.bind(this)); this.getContext().manager.setModalActive(this.key, this); + form.focusOnFirst(); } hide() { this.getContext().manager.setModalInactive(this.key); this.teardown(); + if (this.originalFocus instanceof HTMLElement && this.originalFocus.isConnected) { + this.originalFocus.focus(); + } } getForm(): EditorForm { @@ -69,6 +75,12 @@ export class EditorFormModal extends EditorContainerUiElement { } }); + wrapper.addEventListener('keydown', event => { + if (event.key === 'Escape') { + this.hide(); + } + }); + return wrapper; } } \ No newline at end of file From 3e1b0587ec5fc81f02e5df4ba5544e233942ce92 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 7 Dec 2025 15:07:08 +0000 Subject: [PATCH 003/204] Lexical: Fixed undefined entity selector value Also added pre-fill of selector search based on selected text range. --- resources/js/wysiwyg/services/shortcuts.ts | 18 +++++++++++------- resources/js/wysiwyg/utils/links.ts | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/resources/js/wysiwyg/services/shortcuts.ts b/resources/js/wysiwyg/services/shortcuts.ts index ead4c38d432..c4be0f3cf2f 100644 --- a/resources/js/wysiwyg/services/shortcuts.ts +++ b/resources/js/wysiwyg/services/shortcuts.ts @@ -71,13 +71,17 @@ const actionsByKeys: Record = { return true; }, 'meta+shift+k': (editor, context) => { - showLinkSelector(entity => { - insertOrUpdateLink(editor, { - text: entity.name, - title: entity.link, - target: '', - url: entity.link, - }); + editor.getEditorState().read(() => { + const selection = $getSelection(); + const selectionText = selection?.getTextContent() || ''; + showLinkSelector(entity => { + insertOrUpdateLink(editor, { + text: entity.name, + title: entity.link, + target: '', + url: entity.link, + }); + }, selectionText); }); return true; }, diff --git a/resources/js/wysiwyg/utils/links.ts b/resources/js/wysiwyg/utils/links.ts index 03c4a5ef075..a7d999d0c27 100644 --- a/resources/js/wysiwyg/utils/links.ts +++ b/resources/js/wysiwyg/utils/links.ts @@ -8,7 +8,7 @@ type EditorEntityData = { export function showLinkSelector(callback: (entity: EditorEntityData) => any, selectionText?: string) { const selector: EntitySelectorPopup = window.$components.first('entity-selector-popup') as EntitySelectorPopup; selector.show((entity: EditorEntityData) => callback(entity), { - initialValue: selectionText, + initialValue: selectionText || '', searchEndpoint: '/search/entity-selector', entityTypes: 'page,book,chapter,bookshelf', entityPermission: 'view', From 2de3247ae485897539b7d9967ad1787444081abb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 19 Dec 2025 14:22:27 +0000 Subject: [PATCH 004/204] Deps: Updated PHP package versions Includes major version change of antonioribeiro/google2fa which changes secret length. From manual testing of old MFA secrets and new, this should not be breaking at all. --- app/Access/Mfa/TotpService.php | 9 +- composer.json | 4 +- composer.lock | 118 +++++++++++++------------- tests/Activity/CommentSettingTest.php | 2 +- tests/Activity/CommentStoreTest.php | 2 +- tests/Settings/PageListLimitsTest.php | 2 +- 6 files changed, 68 insertions(+), 69 deletions(-) diff --git a/app/Access/Mfa/TotpService.php b/app/Access/Mfa/TotpService.php index 1d1af451ac5..f13637a3f17 100644 --- a/app/Access/Mfa/TotpService.php +++ b/app/Access/Mfa/TotpService.php @@ -14,10 +14,9 @@ class TotpService { - protected $google2fa; - - public function __construct(Google2FA $google2fa) - { + public function __construct( + protected Google2FA $google2fa + ) { $this->google2fa = $google2fa; // Use SHA1 as a default, Personal testing of other options in 2021 found // many apps lack support for other algorithms yet still will scan @@ -35,7 +34,7 @@ public function generateSecret(): string } /** - * Generate a TOTP URL from secret key. + * Generate a TOTP URL from a secret key. */ public function generateUrl(string $secret, User $user): string { diff --git a/composer.json b/composer.json index 7e7412976d6..9d47f5548a4 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "league/oauth2-client": "^2.6", "onelogin/php-saml": "^4.3.1", "phpseclib/phpseclib": "^3.0", - "pragmarx/google2fa": "^8.0", + "pragmarx/google2fa": "^9.0", "predis/predis": "^3.2", "socialiteproviders/discord": "^4.1", "socialiteproviders/gitlab": "^4.1", @@ -47,7 +47,7 @@ "nunomaduro/collision": "^8.6", "larastan/larastan": "^v3.0", "phpunit/phpunit": "^11.5", - "squizlabs/php_codesniffer": "^3.7", + "squizlabs/php_codesniffer": "^4.0.1", "ssddanbrown/asserthtml": "^3.1" }, "autoload": { diff --git a/composer.lock b/composer.lock index 98f2d460634..cd4ba68c56d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9f946fb1755acd72dcc63d0c3af637e9", + "content-hash": "556613432c8fb7d8f96bcf637c8c07a9", "packages": [ { "name": "aws/aws-crt-php", @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.366.4", + "version": "3.368.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "1861cc8eede21cdaab0732fd44f43f19ddf1effd" + "reference": "96397db9a3fd0b5e6b3c52e363b6a55831a93b1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/1861cc8eede21cdaab0732fd44f43f19ddf1effd", - "reference": "1861cc8eede21cdaab0732fd44f43f19ddf1effd", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/96397db9a3fd0b5e6b3c52e363b6a55831a93b1d", + "reference": "96397db9a3fd0b5e6b3c52e363b6a55831a93b1d", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.366.4" + "source": "https://github.com/aws/aws-sdk-php/tree/3.368.2" }, - "time": "2025-12-09T19:21:22+00:00" + "time": "2025-12-18T19:07:30+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1739,16 +1739,16 @@ }, { "name": "laravel/framework", - "version": "v12.42.0", + "version": "v12.43.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "509b33095564c5165366d81bbaa0afaac28abe75" + "reference": "195b893593a9298edee177c0844132ebaa02102f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/509b33095564c5165366d81bbaa0afaac28abe75", - "reference": "509b33095564c5165366d81bbaa0afaac28abe75", + "url": "https://api.github.com/repos/laravel/framework/zipball/195b893593a9298edee177c0844132ebaa02102f", + "reference": "195b893593a9298edee177c0844132ebaa02102f", "shasum": "" }, "require": { @@ -1957,7 +1957,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-12-09T15:51:23+00:00" + "time": "2025-12-16T18:53:08+00:00" }, { "name": "laravel/prompts", @@ -3961,16 +3961,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.47", + "version": "3.0.48", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d" + "reference": "64065a5679c50acb886e82c07aa139b0f757bb89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9d6ca36a6c2dd434765b1071b2644a1c683b385d", - "reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/64065a5679c50acb886e82c07aa139b0f757bb89", + "reference": "64065a5679c50acb886e82c07aa139b0f757bb89", "shasum": "" }, "require": { @@ -4051,7 +4051,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.47" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.48" }, "funding": [ { @@ -4067,20 +4067,20 @@ "type": "tidelift" } ], - "time": "2025-10-06T01:07:24+00:00" + "time": "2025-12-15T11:51:42+00:00" }, { "name": "pragmarx/google2fa", - "version": "v8.0.3", + "version": "v9.0.0", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa.git", - "reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad" + "reference": "e6bc62dd6ae83acc475f57912e27466019a1f2cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad", - "reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/e6bc62dd6ae83acc475f57912e27466019a1f2cf", + "reference": "e6bc62dd6ae83acc475f57912e27466019a1f2cf", "shasum": "" }, "require": { @@ -4117,9 +4117,9 @@ ], "support": { "issues": "https://github.com/antonioribeiro/google2fa/issues", - "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.3" + "source": "https://github.com/antonioribeiro/google2fa/tree/v9.0.0" }, - "time": "2024-09-05T11:56:40+00:00" + "time": "2025-09-19T22:51:08+00:00" }, { "name": "predis/predis", @@ -4598,16 +4598,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.16", + "version": "v0.12.18", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "ee6d5028be4774f56c6c2c85ec4e6bc9acfe6b67" + "reference": "ddff0ac01beddc251786fe70367cd8bbdb258196" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ee6d5028be4774f56c6c2c85ec4e6bc9acfe6b67", - "reference": "ee6d5028be4774f56c6c2c85ec4e6bc9acfe6b67", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ddff0ac01beddc251786fe70367cd8bbdb258196", + "reference": "ddff0ac01beddc251786fe70367cd8bbdb258196", "shasum": "" }, "require": { @@ -4671,9 +4671,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.16" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.18" }, - "time": "2025-12-07T03:39:01+00:00" + "time": "2025-12-17T14:35:46+00:00" }, { "name": "ralouphie/getallheaders", @@ -4797,20 +4797,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.1", + "version": "4.9.2", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" + "reference": "8429c78ca35a09f27565311b98101e2826affde0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -4869,9 +4869,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.2" }, - "time": "2025-09-04T20:59:21+00:00" + "time": "2025-12-14T04:43:48+00:00" }, { "name": "robrichards/xmlseclibs", @@ -7922,23 +7922,23 @@ }, { "name": "tijsverkoyen/css-to-inline-styles", - "version": "v2.3.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "0d72ac1c00084279c1816675284073c5a337c20d" + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/0d72ac1c00084279c1816675284073c5a337c20d", - "reference": "0d72ac1c00084279c1816675284073c5a337c20d", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/f0292ccf0ec75843d65027214426b6b163b48b41", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "php": "^7.4 || ^8.0", - "symfony/css-selector": "^5.4 || ^6.0 || ^7.0" + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^2.0", @@ -7971,9 +7971,9 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "support": { "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.3.0" + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0" }, - "time": "2024-12-21T16:25:41+00:00" + "time": "2025-12-02T11:56:42+00:00" }, { "name": "vlucas/phpdotenv", @@ -8439,16 +8439,16 @@ }, { "name": "larastan/larastan", - "version": "v3.8.0", + "version": "v3.8.1", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "d13ef96d652d1b2a8f34f1760ba6bf5b9c98112e" + "reference": "ff3725291bc4c7e6032b5a54776e3e5104c86db9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/d13ef96d652d1b2a8f34f1760ba6bf5b9c98112e", - "reference": "d13ef96d652d1b2a8f34f1760ba6bf5b9c98112e", + "url": "https://api.github.com/repos/larastan/larastan/zipball/ff3725291bc4c7e6032b5a54776e3e5104c86db9", + "reference": "ff3725291bc4c7e6032b5a54776e3e5104c86db9", "shasum": "" }, "require": { @@ -8462,7 +8462,7 @@ "illuminate/pipeline": "^11.44.2 || ^12.4.1", "illuminate/support": "^11.44.2 || ^12.4.1", "php": "^8.2", - "phpstan/phpstan": "^2.1.29" + "phpstan/phpstan": "^2.1.32" }, "require-dev": { "doctrine/coding-standard": "^13", @@ -8517,7 +8517,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.8.0" + "source": "https://github.com/larastan/larastan/tree/v3.8.1" }, "funding": [ { @@ -8525,7 +8525,7 @@ "type": "github" } ], - "time": "2025-10-27T23:09:14+00:00" + "time": "2025-12-11T16:37:35+00:00" }, { "name": "mockery/mockery", @@ -10372,26 +10372,26 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.13.5", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" + "reference": "0525c73950de35ded110cffafb9892946d7771b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", + "reference": "0525c73950de35ded110cffafb9892946d7771b5", "shasum": "" }, "require": { "ext-simplexml": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": ">=5.4.0" + "php": ">=7.2.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" }, "bin": [ "bin/phpcbf", @@ -10416,7 +10416,7 @@ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", @@ -10447,7 +10447,7 @@ "type": "thanks_dev" } ], - "time": "2025-11-04T16:30:35+00:00" + "time": "2025-11-10T16:43:36+00:00" }, { "name": "ssddanbrown/asserthtml", diff --git a/tests/Activity/CommentSettingTest.php b/tests/Activity/CommentSettingTest.php index ad82d9b704e..f8210114c46 100644 --- a/tests/Activity/CommentSettingTest.php +++ b/tests/Activity/CommentSettingTest.php @@ -1,6 +1,6 @@ Date: Fri, 19 Dec 2025 15:15:23 +0000 Subject: [PATCH 005/204] Search: Fixed pagination not considering sub-paths For #5951 Added test to cover. --- app/Search/SearchController.php | 2 +- tests/Search/EntitySearchTest.php | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/Search/SearchController.php b/app/Search/SearchController.php index 2b9ea79740f..8a6a5bbdedf 100644 --- a/app/Search/SearchController.php +++ b/app/Search/SearchController.php @@ -30,7 +30,7 @@ public function search(Request $request, SearchResultsFormatter $formatter) $results = $this->searchRunner->searchEntities($searchOpts, 'all', $page, $count); $formatter->format($results['results']->all(), $searchOpts); $paginator = new LengthAwarePaginator($results['results'], $results['total'], $count, $page); - $paginator->setPath('/search'); + $paginator->setPath(url('/search')); $paginator->appends($request->except('page')); $this->setPageTitle(trans('entities.search_for_term', ['term' => $fullSearchString])); diff --git a/tests/Search/EntitySearchTest.php b/tests/Search/EntitySearchTest.php index 8501b65c43d..cb1149dd10b 100644 --- a/tests/Search/EntitySearchTest.php +++ b/tests/Search/EntitySearchTest.php @@ -30,7 +30,15 @@ public function test_bookshelf_search() public function test_search_shows_pagination() { $search = $this->asEditor()->get('/search?term=a'); - $this->withHtml($search)->assertLinkExists('/search?term=a&page=2', '2'); + $this->withHtml($search)->assertLinkExists(url('/search?term=a&page=2'), '2'); + } + + public function test_pagination_considers_sub_path_url_handling() + { + $this->runWithEnv(['APP_URL' => 'https://example.com/subpath'], function () { + $search = $this->asEditor()->get('https://example.com/search?term=a'); + $this->withHtml($search)->assertLinkExists('https://example.com/subpath/search?term=a&page=2', '2'); + }); } public function test_invalid_page_search() From d504b191434eaeec78aa59d7505c631b0ca20d20 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 20 Dec 2025 15:46:15 +0000 Subject: [PATCH 006/204] System CLI: Update to v0.4 - The init & update commands will now use download-vendor logic instead of using composer to install required PHP packages. - The init command will now use our source.bookstackapp.com git mirror instead of GitHub. - Updated depenancy PHP package versions. --- bookstack-system-cli | Bin 388792 -> 393659 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bookstack-system-cli b/bookstack-system-cli index c55c5a8a7840523d5de1a2b1ee74c13723e1eb6b..1a518e644e476e47c05bc8138c8a8bfd5b0784b4 100755 GIT binary patch literal 393659 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

b%oo;YMW{IwPIYg$h54kBfVvGu|g z4@Oi^(#K^O(iqW$!-kE-Mi}KqAERz$6X?W)D|tC5kjGd|84U%#YZ1XAq=%-i9stdA z=e=r-&=_%IG>Rt}D`4%410;y*s2oW4Xkgq8DF~1)1twXk)d< zSR3TfKx(_Q$P~6}AE8bP9;e!@d4)5-7%^nSD4|3G6Z~8z-Jb@3-H-hpdkgkkNWu<> zkgx>0c$`|*jgc5}1ZGSGj<5Vfyh}h}xkA9}!oHuJrFAoKkJ|8GaM7S=Nd;fj16!$_ z1|jQu6dkr=l@hfRV?Dw-{~Nj$*y|jHT#F)a0k4x!r*PKt|1&cDahoxb;{o!6l50sA z=Mj$QFPzcetpT%=QNR~a%F*D}QM>mc20X%?#{@=CLjHcJat^R22YzAB;ocGM`|m)b z#_J8>3*q$?{3C9R)lduV=6wg8ejv&p9Avj3VnOhVwUDK(#b17?%D7148dJi5eKt-)vzN z&?sAQL~WrOFjQ^8OJIoqC?fmI3{d5|s38mhyx>J$(=Z%!fIFUNfk*wpm)*gkUvMCh zKxfaOkk?T(SD-ZS0Ju{(w{Yf#CjSFG_-&ye651G~ejc3$Q-D&V-=#dpsE;`OF{bT} zMIHX%ghoKyp<01o%UGy7iL)GI_TOoP-vndNwML+)pv2t(da+vRm_`F}zOKRHDNfqz&p$AFWC7a4G$a`hT# zXj+1@|EL%E6;JS^FK|g=gmR9f%2@^)2wvpQnQi2zQN->rrmdF2wH9V#b zPz&P*1TS=--im?!EngWX#(oETWf$s@q021Wu!Hm6*uUi&2o(G8h`#Vg<2pQIQrq&0 zV>=@@OUIFtjW((W7N7&Q0`x*gvJGb+;NP?cX8d4aazu4NyFeI#UY)mta|UC?tZYnj za2f-?U=DuP2`bD96|o$k)!lu=S#VJX6vrFgY!tu%bP|WCpjR$QX#|FbMVogs@G_0O20$yz))=vB&SbwNhJ5gw_vv+AHr#nzSrZ zAcgOau32!?Hj7vMdy3VGmy#ca4vF}=e;#T$;~)agXO)Zb5_NO}8`2G5PoH^G$H<#M z_2{}yxhIakU$U_shfl4!woIes7R5U=b>&AZXL*LT@?4!$BB}ny_EB`LRFir;;h`tt zlLjl75SH9=ok9NgJl3lA?9xQi{z{?F!3oc;diELS*;datH%sJwC>a~6;=wF#)hgAB zKe+mIvho=623*Fl5uCEt=y?d)f zwo2O(9xq%$ZuI2>p1%EaB3_kjEnB3$*_Ao#mg_d@?zy_byAn@lEf9+`dE7XIAr;zM zp~xjNM8BpW`}E>xg1jwx@02Eu^6gRSdIx3|5x&Sj*3szNaAl#wQjz9m`-5eM9$N@$ zdj<+e+olA;Xl0OiWR2*n%Ma|7u6DO<+5A(Sr6N3W*Va>Kye=-f zS=wCXr?R)h5=JNrh)MdjP1 z^yNM#{a+}i`cv;3i^r_opRVbLkC=Dwa>PKw?Lvj0A)A$^Y`;EX)vTtTVCDD#-#Smq zYhQEL%Np{*rsz*Iof)5_`d=^Ekt}BW15XTZDfDD0O1+NmY$K~iuiq@P;O?1zpFwJ7 z@!FkwUoWqyi;<&!>E5?*eN&--a%y9P@ugP$FwhXfY7!jTu_=PImpr-UzY1yLf~h4&>a8 zozkP;USogxr{{~om)4DXu37>Tl2+dc%FU})&3N$IBx#4`XP-`RogQP5QM>85w&CfD z*;@sc9$Tif+fiO%c~e^6+ribW6y7_ZKJGuRM#}K*PdItn_EA*&S^vQIJ9w5K{S@cj z7t7NZ;k99L(%czy61NmhKmU5m46im1b)HX?56A8izNw$~a|8MFvTMtOf5^z+oOa>! zI0qm-U($rTr+8(t}&B%LDe6BOy!Vk^{T?!qqRo<;-7x>#?Yl>sp1JV_v9c{k+W+7u~IJ+_5uB>H4$9J*vBxT5>1tu~7Wj zURUnp*THqer91iQ=1X0l!c*M7q+Z(lWy0W`ndIWPt6De3KU?@{LQ${Mo7I!0-fypY z{CQ9IEyAl3S-pU$7fX4UE|nRJe}`y*bI1y{B;P`5mYAYKha%b;RszxbjHy$HJJieMU}inD@CJPc_~V9rm^8 zn3Od6V)ecEM$dUJGbMGNmTq7jdd^i{vGd)cj-=it$?FBGgr?Rt|f7MwVv_^X`T_vG8UW3HLtNC=TThm&!vpMS_{#SNx`j$I%B#v3avT#G@#D# zNrKtzo>b|c&71PLR`xgENe#1)EUj|9PJUi?@bg*EWf6;1CG%LWx9T%IDmKZ#Fj+Y* zT_DH*vb*nG_08RJv|J&t#?A9?wh8MNBzg%_a`G9}RdE&}LM?{&4zhFT1+%@Hy{FO* zdasK63ZAS!&}?#Ep^q=wd%47w5eLFO9`<#hH8Z| zn)1R8-Unug74bT4_4HU*DK}X;X@xM8WVShY z-6D4Sf^S2f*RA`P0^~Y73(wXFWOQ|iQHAy6<2F$b96GMMS;e^{u&R%1`GnBGGwzuy zr}EBD5H?`UwmooTqJd~GbFJ>CCVo#fakDf1`ln+qRSPLEoFvw667ABnw|9r{ZH?x0 zvDwSz7?WRoZCZNuX^>I-^-or55B8}&`Pg;Z&{nq6*~v#ea9{1$C%TzAuB+4sH*Pa7 zx6YllGjN%F#SH0&Y3GJog!Y7mOeN~&5{xIG*r>0JtVZUeV-LTf; z6?xYOx$jCpna7WNTQ;~$W4EPTvr&WHOLtRs(TRJ#`-?g&x1H({|H^HPZx3U7-<$fH znN@jWUd9XCouOI0bgAj7QtKaQF%4X~)vmKnC(dYpeZ#XQQHLvQKr{w-!%(IE0*~pM zk|`2Ana{SWseN`O=f5|*baTRj)koq#OBwU8`+6jbzDfCDjJeL^&aEl)-lqriQc}M~ z>t+m?UMkK~+~0HFS|&$#pv5=pwa>$^wWi`3#b1|EbCS2ZwVQ;f>h)1xYo}L}VwqXT zvlfIk3VNn>JV^0)n;gUaMq!&&WJGn14q><78I@0gjfK?y}((^&sAT#4+rj1|K7rsCXpVI%3lfrKdrbG8{LclOvc>D}CUkfFGH_02_RX5|GA zHrQB5zcSf2qc?w3r|~52e#Ywex%<1?LvCnAb`~VOx^8)Og=L(|)yGbERx=(+?6~}> zNcx@hzBg~{w5LARSNOOs;AgY4&+`SekmQU973uH4`!4Yfi0OF~M*sOfimoTOQUA=W z4P_UlygRyX+y+Q%X`9g8CRFV}_^isav_xDX^n>xCO}&}a zoC*i^SwCl_gp`P=X5G(J>kH7nd97yUgHAE(+g9t2B?%>dLJ#|N2H$DgJ$aT^Wt6{x z*zed8x_Q%$8B0G_xl;_Tf0(!LxaLH)8K1W* zPCOcY@b>ymt`b%seIC#*&U&~eGqaHLT8XuCgW$sJ2GaTb1BByE%i6@hOz&Kq`Q`H) zi^Msd>$#3UTr)k3HlgWh%)M@zj)>El=J)I8g&BqFduwH{+OyB-R!(>jpP! z++4`FNpqUX)5|%vC$i3Mi;3VjH4|9&a;dep+qH|Wl^@n$B0o)hDlg-F!gcSHw*?1n zUS!G~x_Q7_a$)7HtL6LmX&!DlB9Pk`?{I9YlV0%DuO15vx9oWuxj5O?f!3-y?ebU8 zKA|g)tMVV-Uw)2cvx#TwD=we4s!mfy+NXxfI~*O@ocVKozh_#umuAz_4?;d<423hH&kIo{aMJALvy}OyMia`$tulo za@*GY>`JlfGv@QP+V+H;?4XIQeNh5IbG^0KScjLJJ1ZNN`8W4%DG%>8fAO*;*&)pN zA%Co63MqZn_YeV%i8IbxOmUeIlfR7zl_(LD zdqX?!T<7tgtE|w9h#L<&QXaLl#LOd1UG~$>xb8M8y}W#Eq5ng*2!XV>0lL9FNl*Ag zPCdSO;l+JJ>JK-{0$l#otPbC4)BHoO)9?66yzGvrZEt2gntAxk$`vJBE<{?pCN5Sz zY=2Kqac|(U*V(fk=!92^v_|Psv9TqXdDWeFLH9v zzB&1|8$0HF5UJZQ8dyC^{(N@V`_hXqd``r4oLyeymmqScQr{!7c0=LKY4wROzJK>w z+CRg-X6~viivkRNd|>#pG~vq1jM~yh+vB*mtE-8PUtennwl&x{uRnb} zEvJ{MV%&T=o>tmuQg&-eM#2HF`wBzC{-*{DSJWPx@N`|OgI-6$=S0CXj+}+N_5fb zyahkLN!TB=v|6!#XTj8IQ(La4#`3H^dGAB%^E-N?cb8?zPcZgab-?YTAon?)Rs0fC z+oghz>t5Z|uca@)@{06mqa42+7%Ec`DC&W&#Ut<7R4<+EG}{QY{y&sJj=H~ zMNcQsJ1n5TWg-=KL*rA3OM$m(#n40BhmX!M^ff7FE%#4|-JTrJuc0n~x5H3~aa3@L zPQsZj>DuRX#a)g|eHNWmS=ngwR(D-{DuG~tFPB^Wac%g;v~@;2ZnNil*pfdUwODL$ z?Ma*Hsr~x}9{T1bcvU%m+o5Wni@RS|cI%)+>w;eXm6@4~PJCE5<6CmU&T2O? zo2t-{FC@=znx@8EYrX2cW0CD63;9*iTHxm;B4kTA4Hsk~stk`)FHxwMu zkrhanNa^@~dbY!72XP+Ui-t{AGrmY(5mdkAaXU6%#2N6+WNa?tS&U?g8;m`aaUeN`~l!IP)<1?5bBW3Jx_xn>8b2 zz3VWvAMQ9)Y8IB}rsa^9-kY&{(~7f|QO{gWg9XwSRVRJ-`q%3EyA-{Br7b4>{dL;r+Lid0T7fZ}J6h5MJ`FY})TW$0%iP zMrK#z%Pe`7fJr}@cH7R}3O#vDNj$&UQd&H%pdbI4sA&>qS?$^LW8=VzyfbMN#6-JO zsPFU_EKNDMW2I7%p!JUinFE0mZ61${OlDk~w=XV*5zpf;FI8XOuDQ?o;^}VZ7u>4r zgYPN_A4t3Q<>Y~j(cNv==G;bN0u#H>T(J zSfA6n|H#asrO5b#|Ar|q76j65_6mjxOeQAD9@rflH>qH1SfXT`!sZoG-u?k00aZ^M zS7~ft6{0KoK!4(kJUt$P-k&n8(P`Gb_FYciEhvDz?oxrtNkQ zkJls=2sF*uayn@F>_TNJ!|aJSHB;mTX1+U-FsEjg(d?sYjC6~QFY7-vT2zE?kkg)M zV^~$(eG^}JzV+E5sb4W{C73j{SW0rO}iS z{+~x5uAcKvRoVHH-ii3Fhcs+|-drhfu;1^AbEwslrnC3>8LaJ&c@!4!{m*hw;u9V$ zTJ3QvcV{|J{+HTwZ|p(`W*K3lDUV= z@wp}La`Z=2gS1{!Y6ltCIUn3BE8c$R&tBf@(p4*2{z7GzS<*8VDNm{%cWkBo7S%Aq z=0_*h%~zhiS956h{WNM_jqy#XXWA)sH=o<|e=rT?wU6V!;xe@H+mEac8KuP7Slxtm zrrxQ1;>Vg;dz~Uby{No+=+Hx7m())a6mo>WGH#^UEf!Sp@xQyEEYq)gqH0CDd>zm9 zZ~GF%=in`G9Fy@4Tw{On@y5QG{N?>pc1DjK4jh?KtF3DMIHOukG2+s(HBpt{3-*4A zOm}%YMaAomtF)c;ogLD&LQfFCOpEuFb!;x8jR1za96X zg_Ct{BzNAre()Bfp)2X^nkr)YPe0PF(-}Y4_k4;>_dof0y_~dm^%pVEqSM{*zC7EtuSag`oOv1=i*2kjlMV^&OE{-|Huu2XjG@ceixnBPzg^e~d zqx1QP4)Io64tNW5eXK6IT|Kya@^agDBa^yCu7~4~ZB{;e`vdot35@x7uJo_ua@%7r z(xgYunbN#sVDs^f+0JHLUaz}(#CV=z4588>aFCjFj3%A5<3V(`?zZ;JL#utSEjj$2 zKjPu~#SdhLax-3+YRf5YsWkNOE7o{0&GD(4>L58u_}I(^1x}G!ccvUz(Wv}d%UV!9 zIHhL7!iYmc?~lvx{-RUue{0p@WijPbE1Y7&y5~*_k_Wree3$ACcR0z-6e@1AM zY1~qgyOacr<5N1?rnT6(XDGN{n`FHs6TlDy%lF2T!*0jGjj_}zae*5#Cp6JQG z$p@cV9cS5CWPh4H+eCGJZt&KI{%4VD19+F<$b0UYE^6Ut&b8IjcH2(svfuW8do^*u zUsGbvon;fnWFp1mska2D_KES66N2;4y^uYr+>yE|FZZWUo5;PLBHv!mSMk_!&Tvh5 zqVdLWreW^;?DaCT4^~J_QhuF7BJvy=kCvGd_-mA*4!?l0ya^2j}TyWRMH z!VbgEJrlevPaoi3I(da+`S*R#2Dk2sZd;#kH^AEH^x;X9=(s->&YyYL$1( zNg2~7`)j3N7xZ>F_4GWtbt~`9`i}>KL#}v!_L34WU9frPofxqXM~n~kx-whuNiIAW z@p!R8qW5dbW5Id1tcB+oCdBW{)R5NQ+ug4gQ7RU#aq)6`&%{{msC_Q$>>Qq#p3S$Y z;bz!cou1?wl*3G3m~cF)`C8s_wORR&&KG1(*scj53cbJWTC~JY&xZ9IPYivoW%b;! z%yn4i^8Uz=#awz#DdAs^Ya9sCK5ed9>+wNyPZh~yJK=KVTi!0iotl$g|EM}&5-#YX zeLTWFXD>y(a8fyK@Kqb!@I;{)9Z+9G&9cuK^VkR==Kq^8CA6V!ouG*zCA-GBJcZ zk*_m;cBk-*1l)WkIs3tOt-=fK(jP8}PA+?3vHa|my&7v*+|F&s*@|Bb-%)BVHgCz? zEBGVDeC^7|?*~N<9G|r^T=1LGEsI#`RYQFRKllW-^UoLSd?!n%T_caH7x3%51*4lOGQuT3~XW60Et z;1)MZPtiY=cROlxna@D*>vZYt^9Cnc?%gOnE#E9*$xWK@!^QOU8|j63i=Q#6C-Pqw zmEKUFs;MNservuLb3t6a#qQSiiyL-cZd4~fy~nzA&055jXDy|B)I(qM|c z?V;@K6?U0rxjWW51ctd-a+i5CkKaCVF>JeA`!SgvZy%c`rqjy(au>Nv%{cBU9Hrn~ zyt=aYkw@Lp0-~tf$>d;90pAC<0YB7l6Bi!LBwW9d+mq+FkaRog_r4{mF5e*0eN?sKl37t8BuO_$P2iqGc<&sh?x z{K0bKiI{V9PQ10$^Ss!8nP__C*pVq4^3;Re&cDpa$rY|WxMgz9TbHifX=zmoD{5v^ zrCKk9?fu!=Dtq;!O^Q`@)$+66@5JJD;lfSAFT+wF1PIJNcy!S=H%Iq7 zp&ufzF#8FwbWhI@x*qX#kNA+ZL-Vp5|fLI8#II83K!O)1mzX1YrA(&& z*286o)eZO?JD**OKPE03Ug}rcID|LR6CNa}28m>TDQo!D>+$Yt%!TV;yRu)A2b9(Z zi#aC?EPKeDwYm9;yV0q;ZNVF7`WrbP8<_KDQ^UGuzejJDt5PmdJl_rS;{K`_WHALsAbMUKKp$K%AzXUNhn1EC*}eYM=I9h`lso)?B>O@>P5wRxEF zAKk=?7SpaPdjGU#c`Ny($G0IVV;dVgzVvrrGDADxUs^CwzkO|=p@U0;mWVxZ)v4~? zm(SgtaqDUP#|>GskES`?kc-WHa=~2hpq8XmORfEn;O$Wjb9FB)AxGuP1tdSte{^rZ zlp!c z^_+6_*T9?^kk7~%M7M{3Q~#Su4Z-xa=+XgOwu|laaDfrZXLeedqfxAN|^jssC(OWAI+KJ zhrJKdo90LqbtKF%RT~IcacxhEh;O4s+SgUl9`B}W+LY6YrRh~NwhOmwBua0xBc^UI z@XSi6e!o03CY!8(+b)d!FnyJ%XX~s-6n|zCVo>H);y=D|$ zMaz!3W^$Q}^JSmjBfZ&aVh+W6Gvfx<*}Qo~yVQ|;f3d9B zq*8EST&?XA{L5#B#QnT{S01cPm=ykYiwWz4W1q8*Y*S?{Z;X4fqZ6^=L4JLSXx`Ps zvYGnfqVp0yD-vWDf48bvxzH*klK5D7h_^-g{hGZ3#FO2Av4(+yn>}N0ZA;tn{zhY@ zNXM)^?T;1LoL+T`P7NM>J6pQkBG>!roBe%vk{ec~@1;Z-O?)xgf_Ja0YKB3RVDf?+ zhrA*;d)taWoRk-MWT z-LClzwE6Fz`}_$$xhHM+l=`Y$w(I)N?aYgpJ&wmpU#L?6ogumI z+lQ30x6bMXGd}l`U8B$H`k(wDK%#h;RNhKU_*ztE-uz|Tji}N?cRy4y2jjnbq%AFF zsU_`;Fdy_bK3l`xq0gK>zxC+SSbTr*yy$M;ta4%QrxO}9OK`HD!jEq#4%)72y}!Y| zF05VSSb$MEe#_32p6(6y;X2c=R*0qCK6K*#K&h9ExN%$Co%XrQni@A~&3gGWmf$LX z|5oM=i)k*)gVXP|EMFsa;7t2uBa@wlN5x-H;&B(A`r}F3VsBB8Y>U+s_mwX1tT}m5 zd{Wgk$x!WRdd1^-X+y2FD=s9HjT?`YNV?hH@A30r7YWdz036e|zG#W^xC8@Gg)2##;si`Imd-U)a|3iZ7Au zkmRkZf0lQ43q|YUYLBU3`E=~Q>RnnJ{Ea)=&6(DsDYW5seu<6iUTgQf%W>{v+|r5; zv!a9V-t*YKX5N#hEvau!?p)j0kvhrR<-|7M0>)7LQ2YA$ipMh(Cu%?2Bf0&QPV~il zA$&#QzMVco${(Z8ubNH3RX5GJWlJxbp{!9AQ8~?!zO+Ir*u&ruGQbu8FTcgEJky+Vg#ox4E+Gfp)z3g#gzQN;YLB(l# zZAOfm`7s;qobzi*<<3>*7h1kr;giT;cCYKtq;8}V24~GU^OQVzqp&aPDE)|C>gKzG zj7;rS1%596eGadm-_t6el$qdR+ud}vWT;`%f}SM-Cad4`Cw&;C)zHHx=J7pEId{2G zU-R1{Ndf5%jvI2C?vyO8tU>rMR}%(tIHn7>Dj7*S&EjkFGy*%2+s+~ zi6ZBJGW@<@%DLoe!xE8RnOJ29o2ay~_N<92?td%NX-ceS% zKIG?##=|9U+pbv{?iKF}Z*S$^Ns&2JnOf1JLJM4Rv%x?3%OROhs;9y`Cx&-D5PCVG zq)6!U4ZHc*4=(;#($Ts~>f4kpE~a)mT0)n5^CgZIEH60iv%oD+y*RpW^W7-nWRY`3 zsanO)id)@ne_pJkp4JerC+QgDX#tN9CaPU5aLRN_-(|Se@}B-c0Xg-Ehko?+`WC zuG(EOvYJVDB}zI2xob(k~uljV5?@ zc5-qkeOoE~12Wd{l8mlVd$x1`XLyA)7^wJV9lwySd)aF6tB*MFh+7|$P^D6L!P{8w zX%E$myvqVTxW1i?M-O+C$JumzUrx$+jem3AnwB5`BFMWssnY#_AlK{lcIDp7zogaq z4&)oKjrhfmA5DXzuBNTuLy)yqrb3oOthwjp`Q9VHAhU$z;dcMn(G8a>E>H3iHuJ#h zhkl(yk{;SN2G9B^4*uQL5B++xX6rAdRmpDKFc7`#D<**f(b?Qy?buzC2FRri(DahT zFlcFH5miW)9C@kZe=n)UmXx+m5FRXxGko)A;nVqtG)^JJAmJLBAuR%$&?-eUeBT{- zp5%{cQXU{&B;w&a>bVHeSVLwI?nL@Y2eFu=-LMG8z-J!(%u;Lc)*jfEYDE|jhBasC z{1)k;FlW<51Q2C1AfY5oCV`TcMHUdt9hw-9pPWG_5((aLigTAPthcTmiiJ5HS$r6R z#6>MC>kf8qhB#%$`s@yzj#P@0l^azASA*)5h=3h9!vvSYka^|{PfuIugW(Wt9$07R z7R6dEa%vA>mI0 zkuoiTYt2{ZMd410w{?gn=tH$vB-4kfQpsjip6~s&Ju{>WB?%>&Z*w;bxM1CWftnxK z9m&+ru-wU~3lw~=f`-ODnZ?k9H1iBGxJvdio-ebb%lYq*$eMq0msOxYE^m2#vsu#T`PzZ-yk zo;OmL*`u2><4niL?aa2Hj#j~}vbi>1%Mh0qyGFdKmF>yIc3YE~ECj`Di*y2Y#_fP^ zm0IXN2Tk0K_xxY!9Pz|pH830R50zA1YuhjsefO`p3jxcejyt-C(JtLW+K0lRB`u7x zjiAU%EUHMFKAM%1|Gq0Hah%jJrUzqL_a2>l&cUBPo611RnWUBz=dECV>R$|Z!}r-` zk|?_5&QQi-EXB_UZl49?4iAFkaPx<8DY>?JTC*Xqnd#e|gE=hI)+t*1@ zif`Iqn_DdR!73n%uMX=-%C_9oQ%H;8Y$0(7SD`0V##&z-e^m{2wKwvJ`O;D(|VUYVmMK`o0n_zuM@4ZnP9APEIyBC-B8rRsyE9=pt zUCd*^vm`JTql14y;ko} z+b|IQ-oN5N2-2zp?Q2-KNt9|89Uy{)CLvWOxloV9iEO7(mHzKLCvixcwiD7=KJB8e=x0*#IS#o*pOzO}_U&?W+e;%;c`U+&2oN!)z*gd@I?GN6=t;AO zi2I;4)ASW)!q+?z5GV8E9O4lUl=S;x|7a#3QFmRKCp^eYEb~wE6jfZ;Ptt``q}lk> zR#1QuYRtpR-PKM?dC_-M;vqPDAvI!(FI;Km*ywI8$UN~=z@^isTaq%*Nv{Xr=S=Px z4ICIO1-+9ht6Uj?z~}8la~fnCp)HWIwQb?Ju6HrMo(`{WJ$T)?Kf0PsuI{GZub-3g z<%QSnH(``h8)T8$aB71p60(PSg36=4V)2qLVJjg(rOOQSrCj%S!iCjAzpA*NCsAdQ zrYtQpZ0;G!)#!3X19ZA@cnD1)TIiM(n*gOStLP@!3NWJE-oi9PaQ;%cx~8UXAGSht z+Q+js1_KBg5!eKzXEJ%trF?(>;$v!Vv9mv7#4G@vt_xM53YaC{T+y;^K*D zhe;HmRE87^l}S^VX&azGV$8VlDxqk|gw?k-A2JQkrY_^LNiR#+DYalMp_5&{So)|n zLpsf7C;SK>BC!ALLuWt6li>~{IwS5WeqI%pr&5;K6HZ zDq3?>kQVe{rHq?Nvozfevy(uPyt|vEwSq#O%fQa}%ii2JRRaa-8ku4*Y_x3T(2&or zlbKQ$_v9PY1dH0NSHzvsH?4GPVIxL9Qr-Cp|`%xPW zUQog+TdT!53w6MZ#&!!4J15U5RgwZS<6$ID^kJf6w1Vttou--zh3*FIJ?b45@mXW` z&0~_5?TLOh_gK=9Ek0s;bt&N!@s^5LBh{j14%I@-f2W=zPqJw`7XB+`=|UM@K^$#X z`SK-S0(yvcbbxFQQHOQ%L-Zv4HUQp(`~Wy>^}o80Uo@S1^#!$(J#WG=5Qg{siWAC^ zNYIb96gse!sj3*83K?^0tR}YW4`_t=?{(~ilqwZnnj!Yt?|XNTb@valkg!Es5`~F2 ziczyRwM0F;O{Y=BNrhSxim)hIwnDXI6t%ms8evsex!`rS5S0`hxp6zLMTyz7MQxbi z*`g56(1p_rwnr=_s*^i}mrP48tqK1nH>h&g?Iq8XHH|4Oe|Gg$g6B+CqzQVq$UUOg z;L{Om*bZA`bcn!x(r!vdA-9}123W7DyEn>GlX!s_DPfQr+`{=QKwS8BJfJ*hhnO|- zknjgD#KIp0X$qmuV>H+;4a|N<_#4CqKTUcE8zD-sKb-2tA8qF_1CDzbdacC!;R;5O z)Ym-C_-G6{DK)yO>k=t6R&iIVV0tnS{F6D+?@4C7SVv!FjJI*%D33a)c|v?{87Bc#eiP$iWzxFuO3l=Uxx!$xcuG8&2U1TCfX* zc_jpBWCBXE9MaM#vEuR(%z8Ofg6AN({Aq?@RrGa!_{5EMW^*nuE4aqoD!m3Z_uG$r zh0;T&(d#d}(5g0YfBlqWX}OZkPUhcbHFJj<%PhU-mCT)qr{<%kqfQ*&UOuJr;bWPE|5uDw^rY!SbMOO88Zkq_aay_I*ZN7v9jp$Wz;ZwQ25#!APoY^ zs#=DiEm60$WbRCP*7`bjEvHxO`!B`U*4L34HsyshO~au_E3PU#@vO#hr^#ZOGzk6d z{=PqW*d~3hh-kimB!*!^KwSZ}karIR8X%orgnX@xd|3)rVDkCf&Gprf?>GNUZ?7kp zH=Zh;5K3Vjo_v~5aXVItlsdSp=m-E8})&$|jv_Teu)hmp&BH}1gp zsFtssRd-k!rz}R2BoS6+R;XHnWGnvYhh`WpQHJfgyhGOBbc^{^jeX%XmV)l}adw|W6G&!>nUAC7IlJ8uMH>huMSF>xWEZX%C{T14Nr4`0BcP>GOq3=CigIFP{qG%0ltfW-Y{VB?(i_g3 zH^brR_xp66k{BXR6$F`9EYeSUmqK=X9Zm)VK{t@4Gy*c^Z1D(c%Oc3|MVf1fTdiz~S#e;blSCPc4+q$QSUNiv`+T9ZVoaRvRnrHYs^#q)e=AATyXXs034 z5EH5t-I1YPs9gM(%$G=~4P-)-p=KM9xkkhpEf!H!7#Ri42 z?s{P~j+xP%a;wT8Od&uj=F!YooJAzbMP%UQ=_!%|BhMopl!4=~RN_ZF;!*v&ab5z+ zFouNYTz{u}U7$kpR-=b{&9bXIPdAo5mBvA}en{@gyt-MEo7!<1@xGt`3)5?>V&Dq@ z2pCtKg*V8NY8Dm79a2q}JiAGaRG@PuN`s?J{%mp@_(?vEQM)R7>_5nC)|64ut*j%Y z+BN7Jevr+VrdqLN{*ns?GbM875;o_=(bk-0AZDjDNPZK+`e%BE!SyY zi%8XTnNsuCXDACOa=5r?{0<|^xoLX%y!H_cW2$LLOalzgt)aV%-1WvLD15qT`+0~DReN2E^mFw^n328MBqoO2I=!g&DH4%>(?Tw0!kMb|0> z!~LPOJsnGpoRILG1B&NeP}|WA4e>BH5?ayPLa_wVCF1tB_IGnLdwB4nHN4Hg4_0t* z!)(qb9qhjKWKV!&Ru6l2q?w&!RPds34Pk3$_EAJ{N@V)-TKIRaa7SZ&j^! z{?@4u0*6prJ1YuK65wntEO2dVuZ|bDR`u7K{ERIuHA^a*+31ZX-_&xmN6l=Gn5%e{{Wn;QR{!Y|WdYSZr9U)?2m4P2jf z0-f=8bjsWjI>)j3nKtzkPQ0cnf`UOv&d<#ZoG=ddNnhitlhm4Anr-3atOzcWn-vof zTS{im-lW{LrHdaIqrOqxk*CPC711U;IGf6x$3#d?xS7&`EY9A1H~Ym|^?fxytNYG= z@IQrA-*4J55Ps*cxJ8}fR1~^BP0+GwQ>X2rTh&f|DHSr#1*|%DYCHUB#s5A#f#5LI z`2lk0^LO9fclO2SVqQRsOi+!)Sk0N8*mZ&C@K5`&5R4=GE<>%mWCE*N-jc& z;L}y!!px^N@0M?Eo`q~76{(t3?$Bw_c-Ac!&kKPEIjnj@$zM>1&V!g2UTn=r*M9%s zKRvCtPvJ_MQX|+og03Y=;pWE;1dVfg1DCF^Lb%OLX&226FqP+2roA1~7dzK#Y4%!g zH~O_eWIhBIwe(1m0W1_xtJv3u`w42Tbfw*nyI(v5vZx1P;%F3pA|2_; z{nhQ=&)>hg+cbkgFfj>Sz#>gmo> z_SB1xry4d%Hha>paY)%5NOL0Wu?c4Nkfks8U9&q8il&&7ApY>cVQJAHC+|3V-5=SE zqqfYgTBoH2sVq<@(gJ(Yu!X)kR-M)B0}@2y5^l7bX`0`bQlcjDjN7;ialbUWKjpIT z?IbxHVKs%{RVh=F4F3z`THS*2_8Tx8VAT4~H>2sBz5RS^B=%8fqZ!(3cPZ%%$#H$X zO3bCUWDAkN=--K}E(!RW8ZsmbA4A9}_xEhc?cB|b5Nhe)z8Z2cmJ#&8zXU2p5;?QG zW1O^T)xVp+?O0oH+cp$__pjiLppuI?Nmrm7>ZYCNC~L4J4r~|g5Cs||(X~*C)JQ6> z5%|CF98!{HTXuGR7%)B95_v8k&y9zC^DdiZBw}GgC3BUQai{~m$e22P(do5XoTf}= zG-PCu#QvDchd5*kCUV7y_cdghjs@?T&+#Ii3cm0MBFzM6T>Cdt0JZWf!g6F1rz{%g zx+b9o5R@WUi}7#CDCeBX)?Y+6hlH`;)4l}k1>glf#R+j&hML>w97#|q7XuoM8{)=Pf5)m#m znZ`UKlswQRj_@~U2m-hm@!OAu+@;?pC0t^Mj@q5xnyzJQ{jD(f$~l!GLs+9O4S60X z3@vHhyJ^de>KPbPvowROaMeNupBW?zd^^|bM*FV6u)p3 z{%udyDXHqcY??iTM||4x&b} zBq7yU@~ZMg4p{lRS2!pD}om4uq2)@F!)Q#g2Uyu~TzENqY_V9H%c+3y?3qy5{! z-;6+r3dsBaBd~hP2o$-52I;Zxh~{5*!3J?WXbpGLzEH^E?dJ?Kw@tNy3p(z`x=`R#YFzPIx~*h&Hn*B>q~aqQ9? zth2e!ui$k<7hrn~yT-3hhg@7wh9x^dJRc3Opa+EhC8fBgK!r1w(xZytBVsPZ*f~(@ z7W&Tmlm3tWvFG9jRE%AmBP6#_ap**Onl3gJLjtd_qJ(@l8r~3)dFg>1;(fXpPsRk6 zQBZ=Gn~`_Z2SiGz+M=&ydY`<(hl!o)wa&c$<>e5hdZfHVUy??lx z?rTZ9;M%O}OU%c1Z(@c!|8A*8vVIiBxG0*SSuaIs!r65Oul=ty%`)Gh@d1~!qgsCi zDWJsKb3l{io`zr4zfiQDZdZXb6V?SjuBZtsXD2NeT^r$ezEwNup44#L24CX#NoFViI_u9Sbj`q zi}y8wnBODLK(CVe=+pvehHH#P7$mnnzrsOIFI=cGX`%LMO+`^D^^hZiX4<2!ab|mT z`PX^{|Bo~a(=ZXL2(~|^1|QQRRH9*nvn`*Pl3h1DO*XXG1y80$W7AO;KREm|N$oid zm3E=V9QpiOnCk%o(#btzu&uC=#SeIEBg|DoSRL4i7Gwd<+v+|cr3+wuU{Le1wb{B@ zu+}yq^G`u0bgG$!K<0qCgxjop$2G?;)hkz-cKr$*L_agP(`oGgHX>uaFSMclr~5*) z?Dc-I4QWz}+jk_Y^0=vMfVk83=3y#D>hXtS19qvD;K~EWA57nXw3~ec-ict#g+#Z` z*^j~d;dtWQk$3Niv(`D_l?pa`Ys4SM-srl2H-VGs#YP5QA6142Ki~-C`Ote#RbY!u$~P!SevY zUzXn|BtTJ41BDr_Rsr~zJP)L&7wAQtNx+Unq^P3b(4L=VaGr6;iUuCE$Mg%ma)h3@ zgU)Tk0+%$EPt49uzDoDMRfT@ov@)QnGAcdDV zwJA>t_-({lH7M>rFRoC;S&AZ(U@^G|TteXjCME$(d-(j9WEB*9iAHLm!HTOlL%Q`7 z68cghvhF}s0ImmsaR%FvXahTzBq(I#CTs6t1}9j9z)wWzhqXS`6ood7y8%hQX~@Eo zqda0zonhyrV%c-A8i5#t7KzrCeHl>!+gwO|lNXJDb!phRs(i_iy{v^2{NGl3L zC39qImHXm-VB!YDAJWOIV1# zv&HrK>CNT!r`7WO>g4(a9>?fQEy<425Zp_sEPghGLEr~5RuAR9L#-a(jmH1`2AN84 z-#Rw9bM*=&%3Qz+p(tW!B4Ql-Dq!nO_`*=rl$O$+dcV>cMHs7A4Y4af*a$bS=JPp< z{6w_8wo&S3hpk21LHEcJB*!x6)Vg|2C{BpmrT`-S%9n9Z2{)xemB*?(aRYOYWw?lA zSPFpwL(lpgJ4UnF_1Wd$7oS|)Dk7|T@#cp0mUKu0OgI*LSgW2+n$-tuW|0E%_wQ)O zL7<1m_ghmj~H7{b0I zIkpSSkw@9P@gUS!hlz@kRe3`3Bh;yBgKrHmEL znV~Bv%-%(9yT`Xo+E#y7aNEi)@cE>YkE*2b2paUnMy!C1l^W}OF;cZ!90UnDFakIL zCWf{CQuFr@Pr%J?EM+EU(`ol7%d}|OpDncQ1ibCmdQt7IK=mOqC8yj1!0`KF^GaQt zzOwv!p0_-fiQasCRh##8jk<+^hEaW{OH5TGVTm2P=W)xJE$nt-t+Y?~_szn6^g+WG zHO$4_v`-HmpPzjKuzD-#eqRR{;`ZtDdH>yg@q>VcPIc$P8P4pk@@sYjeB^>g ztpsn+wq)q#dD}TBJV7&i&_0H`&kA~2rZLIV@6(i%K&&Rw6wzPj9D`>INr;thSh1

V7yMCh?6t?#s>siUCbIG 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


;Lw*Q`J8buTK^KGD z>iV#P{#4V*NTZaStAbXe1p4TLk(`ERqhYd1eT7BHcIY;V$79_B+?(9v?{vEma2I&{ z&KIBNA@t~IN(ZEfNBU6dj!_v2i%lqv5Z<5f4EN3a!eJjLOESW+WfxzRDIVG?9qN%n zneNnACwG%o_(daGb0g7Mv&7!M*HO-?jzL-|B#L?AGk#9B!xXY7n{UII426a* z!d(VJ-ve>cOWh+CKVZAji3wuhDCdNou$U0P)kd5ST=?8j(g?+9!6-haZSb=b ztNyWZncwb#DXr6Pd_%JSv2r=`-Z4WvHIK1UQ>wY}p2%5;eqr(4yi#;kyY|9XgPyB= zb#8^lE5r1%Y`NX^!I#BLIKA`K^fGk0JwlM^)J7E0sWYv4$;GkI4jFyllvlf@9IuesoA-r~v5P|8bdDXT!fPk`&auw3LtbA&Io54ttoO2ZwGW)Y^&5|2 z{{=j(^%DQKf3!y5v6yy*ktBKLZ+m5O^}aEgC*q9k+voa|kS)v0cdZPGW`a62;pk{CpS{Y)#NsQ7?fF?gb(S`iDPZ$%Hy{o>28Nzq%1D zdxSo|(R(hA@B(3fDjayku%BoCj=-RfnX66Yp^w0%kx2rJFN8-cqluVj5Dn4!rG|lk z2spz4hsMnH0W1?7QU?pU=P*-u-abkT(XF|);Ix>0tTFpuu5P~UB~EqUzma&quPb%~ zfrp!~+r`57BgHws53)Hv&$r!I$7Y+aXJ)=^Z#S;br{{O{tH*XtbJe!R-Rlb7O<7%y zS-w8}Z|knl(dT!MtH<2UbIr|jb~eR+OA6hcS?><~ZSG>8Y zu(___SCjQ_!vD7I`n=)#TyTC@y?PwmJa@Qytk^WyZBs0~uJG4xNkMo?!LKRn9W)Bi z^*Q$Z4rldPxOvXNwwMIu#EQZP$OC5!{`aH2gRyiHi;bCA&D*L2j0p{n9Lp*T-+T|n zTh$2-PT{$ZYGCVuRl0$BR~4}E0CJ+5V{`gd`&NF!j$@J2jf0z``;WJ*QwdPXN#i!& zw0L=r(4wmnD0fRVahQH->01o!I?!}db;@roI0j~ zDLd6$WqQnAVv(XBRGwZqwUusq%*r!eaZj=~UvW>&!C~bC8-tUE|`;^uU#)D-A+rCa~X<6f%uPSCNy(2dNO zzF#d`;s~_Eoo1cI(SUSm|*)&I>!KsqqmSfugfm`!XIvSY=BJ z;Z-_-g!XD~HNEgd`Z-o@c&6_4QN{7Va( z|4%4Kf74ran8MzG2%MR>6Jug`*) zAaX1RU{eDCIxxHqJ#t!!6E$;KK@v4{mNO5YMAqIB%U=Hf;Ggs>kGetB9~{83V9Lf{ zAQ_ruU7O>bUn`nb_4~n!vgr!z;mw>Je7coXrUh))m;pPbWfhUb zgG~J^mlQK+pYAEQ2KjiSzSCck{jW$25;Lc&iN05TNh|+!;H?ksIC-c_J`NPH4CbAx z{s(z;svFIcDtu`6Verw4?>AY2v7`H*QBHO+>sKZA)Kek$L@GR^eW3y_ z;;|Op6yo_)p#mY|u@2oPBBwWPH^VQtQlgT1_zJZR+5JusRJxURq>g(1w}7@gWw!Yr zc1eE!G(OfDohrOzOnyZ7*}qirU`~Dn!Tw{&jXvEa;(lk+2hNJH%)VdVYwBpf7?3&n zBjg73831v=TkRcZ)UULIA?ZT}bQH!%;Z42LyX-#==fr&UyJjkcn3JnRo;&6vyz<^a z`{Oh=aJ=>ReR>a7K>{&IxTU{D%+~NQxbhx-2|QHZz*#}Xxmgu^*%o`(Ewtv;$s`9^t@ceAyQ^IEwacp)MyIsj^-en))cq=xiyL1%7CbB}g>>i* ztbksXx3k)Z1C2LLDVxG49SsW}*9AAtDIdaRexPoA)rc2I`=#!$K_;=IhuXlW65n@6 zDVWVny+DCL-B5whGZ6*S_BhpHtYHjk3Fl$A<{$SsC=U|mLF$>m6-H2nPz=-+9H|h zU5J|d5Bdx{Nn_kV%m#J@3;1^gHjq`52?g6)K*gc#;oe}2eK;TGz`V`n`QhIwhP_)% z3llvTi~h&Rymw%o(bQbX(k)N8pdwW5A#c#qZKk+8F?F?Xt8B_TSujI8Ih>X&G3dUE zR(EzGGF;b=NH%uymv{jY>7Yv+bPXYvRqma+~2^*{|G z75X}FEl^_RCB zFK_?Rt2?_oVbXHFBn4TA#hKuU568%o8V?ns)$MwsjY)!&6<48>71uuWG6NK#uTg^8 zMZBT=Vn{AUmo|5O;MzUBiTXB(HvMZaxegN zoWzzi?}0sfDtT6mA0rBKuHO^T6O+<(fA4*!f@Y-+zxIwg1P~JMUf~K4J@F29vR*`A zTSjRBGVylDaY+WBSO#5Kt0J#8qlUf{V(pfsT|_&<#r$wki}FK%P29?X`k;8C?iGfA z|9Yhu`2Pl_@|w7lgZc66iF!bo_WkR%;t#+7G3612y{u}i7%wRKfcYO4*o~*+1jQea zmjB&C0T-Jk`F{g zAs*Dn-cdG4#XEcOkIq3_a+{bTPxpc_?tdFf*2MRT(;ucA!U6>`&#tj5rR?rNl_Eio z^E?Ywc`x%7@wtEM{}qK@VbqKNZ^K?WsR1;QZdgOK3*qKl%?&iYL0om9H%k3FqkBoAN?2{? zSl+DVh+pd}jM#f{5F@d>n`wwG^5Hlyupp85+Uv9fe!lc+I4Y1NVSF~9X+FDhX|-E6 z+B`0h%t^kiFB9wvf7JRz{Gzd`o=j5m+*EdE;G^DHjkT(~?bB@qCb;F6cJKabx5K5; z;$3zubuJ%G->D6^EmOS^Vqd7?gjRKDDIg2tR`7yB!21{Y;WRuOAOC7tK~-%Cka3Dw z%WS#HU+S-gHfmq8ex*OC{T!ZA{m}kgcAJ^g?|}?A#6A$fZSPF*2ijjBc%U(Dbgcdb z{=d-v7rg$$3kU?VNVth{?DYROc?1K&V(8VfAPGpo5ovM1jUn@j3qz~g>5z)^83qh< zg8q8)?O{5C^-gUd539nZdT+2x6WtKb`X33j!)xWlVFp(yN+XCZP*XsYonj;e#@)kz z6&-#K{zYo+Bc_5@CqzaI_wO{jpD>;4gyId>4J8zHB_%e-aU>8+@8gfd-(xaHbwqW< zawMrkwGgA}uYqnw@c`rXZy@6)722bs@Spy<&H4*GPqN)vOghr_I5-abd={RJaitJL zOuIprI=I0+RRPXohA9heHeZjA?AQO;aJumi>7|~}!!bSBBgElSO?j*kr(w>IHP>_Y zj^HJX?$9G(5rdaTEH~wlp35s{Y1^|lpJXaiyK$j9L~UXv26&Wr~XV2&Rpt3*$~=H zHcky1aWi>vT_8g(wl$;aZ#eLToTf+j!|G02gVf)~2WQrGJFE%iCpgCjXU1ZD*0)`& zw75{jC7-DKgg?DUTqy?oeSXE*&Vl=o_>WT(!V-7l5`L0IaCA~Cz;dc;7Y=4|_lSa4 z#O>s0Q{+BG{E2cj{ULMxpsO3nTd=-z?1>CC{R(p~(vB}P0>M|a5_3UUKO}BZf^j~l zKazI5Pg{$*FXsV4mdNrXeUV5L0Jv&K4iZuKuX!F2+sVNP7;wy_966%y>$yOPzA@+t z4K#g9bGlZ8My28qKE`>AJOxA-?UFgMEbNnp|4Ayt#kPLZW}$V0uUYGm>vYXDYqi4Z zzaTwC3*NB%%Jpp$W)B9c9(aQnTUIW~IvV~ckapz%nI!Fq`*R`c{#I5g^g1f#MzZeX z{5Z4zza~&83zgn|EmWlQQ8`YOA!&YU85m*|vb}bQjMed8+u6nbSCsz5ttuSz4v;r^ z({$=OH13>$j^Y|c4ck$RjQXI{{TTISWgJ%{PKsf6^e+Ikq!!@Fx`~U>|m~J5X0Ls4xKw(OY z{0pQ})9nl1J~Iz_5MHbwRlhZAQNml}5(rx6D^Y&DEL zOfW1j4XYB?5yC-Awuk)-kvEuU_!C8-dmQ_)%`opC${x`k)1KfSF-7QF7)2ZvMaa?1 z&Bt0>p=H~X;#9OA*F)b@Ol|6*Yjh`@>%fu~fD>RBb&hw6+irPQS?dGHtF;}L5 z8bpbb9JIydXLp@jYOQUnbsNMV+#qN-Vc>E#XJBaL{rP0^l_m6RpSjFul}OD>Yb!=Vg7?R*M0E z9aM;3XL< zPz2k}rN?sma@53n>g5~MX1MS;qX$@cd}VX#t+*sVr8n~-(K6sYeHnfp)!LL=(3pG{ zs_SxGc#OYKuJ4MisLs6iG4|R2_nQtV*5Zsm|7gBpPP@kvp4MkMrQaCuHeAqnwf5?* zsCMW72-GsT23$bbckLj*A)5N^TMPdH4RbE_cf>cE{Av`E`iC&;)F?qV*X1PufPYXS z>Kvb+6BKtCptw_vKF9MiJu9m{9DD%nM0cH7k_9aO4NJqb##(~G4cvB%3P6P|C@e#3 zriMyo{}fK+d#-oY7--wX85bi>MU@IRlE!aZqF){*6o{ zP-LcpW&p^Ue$X7@j_zbt=U!l4V0l;oIg(c^4su3nFq3sk)A+2ZmL1?0l||5Qb(U4@ z^>>cgF>rw#!L`DZjRrY_XGJg;4IK0YITBI}0C?jdC|n!ZKQAqr0H_CDqq6W2?qqizTJi$8MGp*K;K@dDcMq{W64=M zxN2T8jh?&bigQ2|p_4+)kR(MDK~P0UAO~&71d$Ks8drQSAjI0|As0m-hWZA^H5)LJ zAWD~5kTKFfp~mZFX-_Tx{4)ho6V#XeQ6J&*v;z`ntvsOdF>}2+QnLqia2Di!-S#y;7sJ2n( zj?f||Vq-t}3G(F6j>%k>b(Ci#3&9%)Qu%+i^ylVeE#`zLRpuNsEs}rM+jrZ4sM1Sx zQ5XzSWBlbv=Hd|c&9~>g=T|hMMT{<@m|n7oolRV-&rl*1&<7*Vj+xLK zht$TsH<8O-0JucS>HygP_UE6&lkv?=Z1?eWoqqU9%x}jPV~#(AAd2Ciktmy;b$GOw zkV>ZcI-QD`6pA*vM1)HM|rFDTJLq;KrKqtBl=&+Ln>m4D6tW!^Koz?wR@jZT`QZtYu z4xEr6DPrehsJS#$uzUo{x+koal3i8#Fl{t=-kkRMTXeXSd<-c(=|QCF;)fh9WcN4J-{j}SRET$Q2PQkoai89fN}6Ss{D zw~r2vFR+h*EH1^%sz*iEI|(@b&2V7OS>=)^x9$j6bBKlurD}-I0KV4su$8ho3em%~ zRwt4#Qp?cQTtR4PK}HM}F9^WQ{i@ribK^D>J_{b&18Oqdlfuy}XC0DQ3)V0sHkFO$ zF?P2y$@rbWDV)gJYC5ZTcAag@b)|jH#od1|VLbqSoM;k_=b@+h3?+M7rxZ#)7Xv8( zzJ?E+t!^x@ed46GxXCfc#qXX7{e5xxvl)F#52C%q_o>-sr6r{zFfWE>u{c+7$v8Qv zE!IXXq_66N3}LyR)wE`#eJHIE=92I_ou+EuI$uU%M!vh#TW{?G=;0JhCoPM*@mk9V zzTetF%QAM{TZ!6eB2E=n8a~Vt7KY{{l~h<*9XAL^rWim3?42hc9$DEFHnMyp$m?zZQ5(@XK-y*(ctsGbc;H85S_&JRp7QkF9lo0FM|bCsv8l(YHKyOwKb;%oB#N5TOOTHKi`8`ZzU{# z;uR9G#W!=!7IjsgYbj}R);6rPdi@DL)qsCl8K=Q)GE}e5{yrWtl0Gc&hypu>$?Ggl9#3bVp#Ti!OoWOx{Bnl@y3KXsP2 zB4FLq0VPUiU3p7IoN+>KB0|w1XCfhHw1Vs;Z|?@(AKCwO^5)rpo@CEVVO@(TOY&_( zCX;k?KQN^ETwsn>fJ^2=cgB&LFQ9cfIO@4w^obLig3A*tL--Skl7k5!hDNQ#K$iI@ zd1T$h*s#gIZi{&OXpuYKD~+Lvb{*xK6nkT`h7>jSf7urNrpe0@|pLIlHh< zf1T7HBX;+pP0XK*LDp9viaI0sZbDFW8iJObVo$M+%a62`c8mxnWJ0*jv%yM{>zHnz znK2cEMaCM0UUk`L*RJoaTe<9W?&Z`c zRiBmeL~|v-kc)*vTI7*SldJslFGv&0h#&Yh4F>mF$kU+Y>oZj#;@UoKWBc(rO6X?i z`Td%wec5E!<+9-0X0+hbK^@V{PI30@!S=cD-QvCP-DQ>6&E;UW*L!@l?{$;F+m2=S z%MxR0m!?`*UHO}f=IdGQQ=7@H=jaji=+S-h>^ftaeDbUyU74VUnxKxFzP{Rfb9rY? z`J1_>x1;9kb?sB1$t~mPk@o13e)234eHri&)KhzJEPpfBe09@&J*<7YH@P(%J*qRg z-5)&?Oqy*LDN$)>-jn2Q>5)X~yo#Wjx9Tn0?7Rw5nV$Nq z%B}P4gvz>tcb2c|3X?B^5if1XODyNEV$(^cg8KR213a9G2QpM$~m&C2i zY+I?wY?G=)>AmV;X;~@StgA|K>EEitB~|aD)}q#q^0v*|GabD-g2Po!MS5@EveH7c z{Zfm=c};ct4jxVwne6eevs3xozvdoX8&xdZc`;^j*TJEFLSA)mI;+ zADoPfqpGy-4%t+@lwOq@PX30ej3Q==Htd|&Cpq889W@@so#fn|=_yvYoK=>nJ-bwu zsI|FJxT-t2{+~4S%Jdy=Q*E^WwK-UQ8}XWoe-Rm8R&KhVIpufulwVzoU*R8KT~?^5 zKeaAXgQ8FW2L)(ClAzcD#GM(f}ljf7+8fy$_e?t(KxyWzIWq&hqaU zYYD$M;scLgzEDTmKVFz(02(Ce<8dfBX~hlH4*3Hy$*u5?@UJM~ z3L?%#e+VwQW7a;79!LfELQjYV=l>I9&#j>Ip#qLC28P&vp(&?g0vu!U{R5SAF$laP))893w5r6q-uT~Cz93>)I(05B*onUuF zt5^mx$At{m9!S#W)Zp_dZAI-)#?tX8iAY$ieAGE2fclknc{?8U`hZq!1Ukp|Sk)2t z+SAcPw5xzyR~u18bg$K)YwAA(FCD#BbR7dnF*P?hmch6((KWR|1MSE_cxp|a)t?88 z0o?01bRDAsd2V*o_MP{dJO{e2=tLN!^RhQwPJv1NHzG?=!6N-x+ij#5fu!s~=}_j^ zG7Oob`BC({R6R!JE)$Oq*(YPbye5^bv)pPI%}?Asr9ROEEk{j2@bJ}SiuT_Xb3T@i z_kSr5Q~xFR87;6a@)@16MZ49e?4Sakv(2yAWisWP_wS&f?2K*dlQmG^i4Obd_$yF< zVF5%(O+Y=~m-6F`MfNZVHUiXOdMts;;06F6RyG5?N zCOfm9s)md9)i3qREYD{m0SoQ+8RB*J9PLJ}OMX)QRX|w&BQOI5lz@N)5J0+J7QH?f zeP#dq!mB9wwH`e6moH^nG}TdZmu97Er znpc3{`_CChb%EWZc^&tJGC{{(`AVNZiJUD@jOv{`M@>5Jab;Z&yAqW?BZa;UT{}~P z$!{$;6M+Bfc$h8ouJg42e>OBR)5UtoJpIC^tT(|%G?p;IIwr`vdntQ8Ci}>%{5~=B z&UyR;6G_H=I5iVE3Ye3Z?dm_ZX!2z_O)3b4aT3RIYS*!Bh0P_Gw#c7 zmv!)Vfh^sBwdCiT7k8(82jH`(V57>{1zDf8>&N=+bW1^w_%47=t6<#MVjSOXYIzY< ze*-YKj8R|5+ddt4ws{YG2Ep-JBFEino#XzN7jE^gKZ)v&6F1JBEIU!x&HLhYE2MPo|M+48I@GmqZI{n z-Gm-$_LCK5O-a`i$v56Gd#|y5G|xaxxAXf%zjM6<)-G3^Hmtib$}`^*leK4&-Lp0Y z+jm_z*jg}ZVKowU;I920-!X8{9uIlVzzwYy)SlP`rUWZ4fCLANWrrjTlVy+O5hLz9 zi8)4`KFK0_oEnJ~dfZo%7Bo^l$&tVvImx2H?N^Ai^F_#Q4A*4j|u$PzSET9}fIS=D+@v zb7}vrFYNc<4^#t!%x__r@)5p?Eh^?ev)7K z9%2>E>3`V?T#+1#{Kr7plJAIrvH7?8Ptqx+&I@hmtIGI^l_Ju}88KTmH&8op5Vi%) z3>~nV0UJr^L`8^!uiD{f)KSuljD;ubN*|(xSEZ3R`hGm>N|}#fkP_Qhdzq0nG84vx zJO=<$xg(_Z%>Ad^+Iz`G_(Cp{ej{qn=!6c5@FHp(Db3^J6MiKXxl8#pp#{%^gRHGz z3GCLu^Vz93fWC#`ZR;<@|KClb_OF#M)0kIJF&_p720wjaX(#TBV|IV&mxLl4<^$ zb9~e(g=6+TbhcTTe^B=|d*~eH;7m&#wU|0NrH>)|h7Yg(G4vQTHuwB7e1OA&KSmaC zIP%Bn1P&+usZEztsqT1;nf5)yYL{j@)1coS6$kWw#1xIOO^T;9@&|94Paj!B6@M*j zdUUg1Y97O@z0@wZ;OuJN61f@1EORB%L2U3q+7dYv0UH7b05;?+Fb4>CSa%3_NcT^$ zB=W>4JJ_fqSzl0xSS8`Oe_X5%903%cQixsZda06iO_7{V`Jv~LSP=YWqyCp>RNH5a zK$*)%5lP@qr;V`T5F+`HlP^j`gawc-2;2cDB1_2L=dHhV{8xSe{DF`(GL~U>2UH6f zcVPLKvoCYR_rRs;o@AaFiTD_}+#UlLT!bOAAt>N-OSuDE@QFM4JALED0a*0q<$unY z+clIm!M1!_^iLpOjsaGu{le+qL|g`ZPT&HwofyF91XK%xW=M>4vX6MDBcBg>;gHuU#pO?qfpu7OV!8T#G&-G=vyz3)ZfMZ)wG!j>#_QS97)RkTd zBe@MsfpCYl#*4qv(+xqS8-#^)+q#4cL?%7LNbLNQhOSCD+E!(RQxu!39)ra;)R`&2I#5jVfqLqYDu z%Tc^{1|dDz3))6D$UDRXRktAM8|;O=pYMMll;a4_!}&{OC>A6UT*aogjOOys*Er63461sji#+RvUY$J3X8J5S~U$*wT-6*k-qsBZ_6uF?vbtos*p#U?)9!| zIJ>+9=ZEUOw1f8-wQP-Yxd?-&X}HlmNL4qUU>B}q#aOVVx3qHD#v$m;rgl-wKy}`D zzPT5-a@e(iR?EC&J=t^pT;IQ3NIFJ!2m|Im{G>{F4mevliRVc;N$`-Nm4dB=IY=;o z?>o>A@U_s3K_?ML zh+35Yp);IEb*3aw4#T?@&CsVBKAJ`z@wkNz_$#f|JuzGIf?<<644V% z1Au{K+xw!o+H7 zZtm}=XRxvVe}*DSP{@PGkyTt2TY^W}RVs5G5E3na>4@y6trmxjG$0Hiz|(pCpT{uLOrX%(%){Scep>*oEt9S9tZ;Oc7T(^@*2 zL{^snO=NGa6<448Cny5w3G&8Cj_@5`r#4lGoQHUJC`6Bw?u(1WKsMV}ShgKUTw8WM zk;kb=A{MCk(RZQ$aW_9)GMIgI6Y<*w$M7qo4|m}4!(|S13}?x77~vLs#QAVL96wz2 z-UfS<*24tHsamzf^W*Z?QNln~*A`HNSzXGSk7|Ttrm8DW(~PEbvSRUgXW&z;TY1XP9N>EaDbhy=)-Yu@A><`>M=lH^8qU1UP2;7@ znm?+KT!sR9%52OqdP|o{KKdkR%>h~r);^R6KY^x@)L%e=WbmVHujnIyVU?-c)Muq? zHhap2thS2it^s&f!a%R&#z2emN3SW{fIgD|W}g$0TFZ>!>_oWvY>q_zSZ8zNMC8LY zKi0Y4SovYMS|M^br@6_?5O{1uFx5Qnj51CV_+mel&GWb&Bj|iJ2#jhcTI6kJm?-3> zBwi5lQJKmY|EgH-jcZ4KwK9mp_sE5w?oqkP(qK5dRTp07ZRS)<vA;O zmK&E_053rRATk8l+U$F{+N^43qCRH4(SNn*qpO%C6c>Axy8d{nCOD$)2q7lx%n@|q^A z=DYYB-F?-~$qzOPzJ3Gq|1-!DnM#QNlEFGnw!mTPVTbQD4##hUQ`f#9bC99A2HiG5 zch1ejW0rLPV>i*(ok&CP@jvcj6c_IQxe9*t=Z*#A00He}oR&IRKS%YDO1TZQ9FjWd*FO~y@H~^$o z1EfY4Hjo-0^i(kbg#by~2PA1akfezKKgB(eg}2PM2c+K|Q=@e`_H%emH$aMf1?U?9 zDq#omA`8e18$9^PQ@o!*;sB%wwhWLsGLbCL|={f_qlDwz~xQtc{c-O>I85$xEKP%DFKFaG%x}T z#WBKtfCI=-YklatnYg8`Q8k>DCxcu{`;s zF{ZJM?I?dr(pk#iZVb?5AG``J4lhfiA&|bnaPYK0*qJl{t5J{SX9ox(R-@w&u7}gb z;v^X0WDtxdB3FSogBH&c+YvxtI3ghj8i5g5<9QmC8=e%_XbdGdY7IERuRT<`I1Ouz0l>~)SAJYg+kjex`P-|`@T2vs|8+8z$CJDu z4osU7Q2&MDS zPgpM&LgmTN<5({0B}Jp>J_fFd7PT*W!fdKOr|kEiFFV#BkFP}tyvFX~Zy@i5t~%D< z`$oH0!8$kBhFE>=BI4aiS%h+eKP zIxT6DCGJmHn}{x(w;lHmlwKg4$bh{r#~od1Za{t@qd zh_n6?$NwXC_z)}nBOU?755lusKIt4?%pRL}xP;D|2#(DTm{<||A-c40ajfGmn?E@O z^RyO>SdAF+hhE z9PiYFOa8U4Qc4}%+>(6p&Hx8OD~~jftwc!$)#$)?U9pzDE409v{5ItG)++x!pG21A zuh*$Vm1z6ni8- z8$k3e@KYeUuk7x;_U|9VBP7xxc9AN*qPzPn0 z*>R<>ai#rEl)i?+<CE9hbsJ6X_)y*P#k{e4|oEd6bC27CX zS{orpyp7C3wQ?I&ND53iEBSfYOdV5x%H`d&5PeH)QQ6bFp755Mia4x&y{;;n-l%i> zu_IGSh~Mw`?6A@UtAM>Z76_-^_lfJ+D8eZb1`O5UV6J!X)~H<)BV+Iy90WF1Z=N(f zTzSMtA`S7k%}nCAt)BWBi{~f|c8R9EB#N*^Et~b!H0mWOdhpMs8=wR8X8ib{$%dii zB^Q{EOjmGAF}(3MXVbcI<2}TLEmwN-m#96Q?H-;k4wChe^j%Av2%45#a~fUp<=1T) zhN4c!`WYj}N=zZL8a6M|^pmxf)JaY!+YV!et>*G0CyI+HJS6yMXhH?5@{%reDHeJ5 zxV84eJO^5P1o{x4>$S@(HnSVnv)fnCL$7XSx=gu&H@!?Oj~j`}2%NRg*;oBcqvSVM zsx-@NhEIQPUrjS}X@7yhq(c9I`B()np{#7p>7BD6G7hK8_t|`R1E-bAWucbuSPl0^ ze@MnBKOYMm55goCnx<8Db67?>Jjacm&ZgPcEUAGdL98&QoYfS#4anL;Hm>k3TN=1Y z;zbW$_|elB9>Zypw37Fr{Q`FAZk8Ek3u#%m!k*LW1U<$I3d_-PCNYd~aPTqsvp;uFH1g*%53)1f~!Zzekf``2`7y z!suAfljLi+?D+;X9SG6TzP5CM=x@N0!7-!=%?ySHAQ?1X$>iPRKUlpwR=x*a{?SQ6 z1@pqRz_jWdH#%P$@qDELLmFi-MLiOK@WK)L~)+>ibwHbmP!_1p#WT|?pJQB8iVFlp0u-ve7G!&!fuOcNR3yc2$Q%L#u1RtxBn zI&-JdDCvR-Mj$aeRs#2euyD4YnfXK#d(#OXB|1VKDq|=iL8-wFW9I;7T1Sy`JSqk+ zGkv{<{gU!hE%8~1W?>DtN-^-8Uo*ETL4!L^D1JZ65SEowDDqoPO(opK?BE} z7UnC=6+{@H;O&irWIXbv3!Ehlx;0FZB%I#r1>Tc%I1|RVaC5(3Eba?L2@G>C3P}`0 zg`lb(a1MM9QoS1JTu_)GU*|*1(CDidcr0^r7ZI%0Homn6+taJ8Ix>}Oq;-DE9bS@bDS<*Dywh&?R%Q=mrFkph?;)yXSL|(HCHJ8=ByE zPsz1(VI{w)PD#-n;PPB=F_=mGog#9uC@g}8O;R-k{X2KGSBs@SD5@GQGJPO1^ghRF z!{#L0H$Xs6eY9rRDt0LAG8)~;vxN>RN!(@YVnMCxVH~fQ`sDIcMXU3`X*i3!1fge= zQYKn8WTq2pz|VfrSj9ZW!5j}Xv&0gcAfs9%5;lv?NL(wtP0E9R_p?+O+-ujns*@*U%euww(qSI| z%%-3z3Mtt485{h99`6$n=QqjrtaMYYJZ8&5=vSsc3X%t}UFUwk>> zp#?kDAuf|hb4v#u>+0Kj`1hgAT1LyIVrP)Cjy=Rd3=$W1q!+JM@bg0NZ=P8VcG#Z$ z#N`S@&0cgPYCpLRgw~)TfPMPRn6B`2!T&Vp*a_A`i^1xPE3lA1!^63VLw03UZ}1QH z&kAML1)P)waTQmY;dkA!`nEjn|1{5jKF(^AbUTU<;HC_!4-a1$2tWFS%3|5&%UD-l zOpezz_5C!mt$rzl4VCmxgJw%5rDqP^#rs-?lf9^!g9|yfp`GPWSs>Irpl!T=Y6E9x0vGP4@`3-d4OxO*n$UA@nkAyzU9gHk(OK`rPNpNz z8>{A+#;e9%*F$Fjeji3nhr-FP+3KjryCw)ruV}Cf4;uPJENE!xBOaV3qqgr*-fy&> z7#8b=)1Ao7gc_w$&_>U~NX#boA;I4WaO%vNdBKllr2p(pte!h9t`y6E89TZ-+r4%& zG1r#9X}(9E)~Xdu>#|g_J(OKfB-kY4EOnETt*&6iGSn`k$=+S(HgD74RM#t~|8oa( zZi*=~8a5Pk%~66|Y!X{mqH=S!?m!M&r}LG5|MmRnX{znvCf(Kj?wM$ww=-nKPL-QJ zO#O3G;yE*@)GP96>;!&{&I0Vvb_qIW#cI#x<48~fAGgw#TY*F0(kEr96YNoSn_;we zs8JpE_Y0wALJUV9J<5m6Imy1Rm^31z_q;_X<8sKh7i z={#ta+yi^b*Q0psNW`TrnpyOYB&C3hFCn+%Y+C+MME2Yj)`61F+Laq zkxJ4M0^GT<8l%m`zq+3tsO_xsb`s`0n)!%2+i~J(?xxIo`&76>6R*`Gr5TM*cjI2Z;b3X&Ll$b`{)e~qQkl*3JOR54XT;RBKT4s?a*~_-IxzkB>vpK0jz=2H&AUe7 zi0h`|#g-{dFhtRaqKbyM(e589*wp(V-oyJ?ED5bAA5p?v(aO=wQZ6OY2!nD{>^oj# zU)Ao~WhNHNs==q1rq2>ETwLLnfliYsgUMfmtV+k8c<6M+0(|&jiZ_h2GBQNPjj+8! zK?2tT(Ib(8b?eilg|T`swq{4Kmq)YpgAJfBtyN|#AxDCK6J*X|Zl39K@dXAZi$-<2d?GDuDo^#X zCeVtGjD0|*-M-NnvQ0Sp>nXd3_C!1+&qhBC9>@{CT`RhKRIM5uBS9m)+}{FfZ$*#q zmf+K}?!VJ%e9h2wlxf4QIGbxrItFfY?)T@8vs0z<6erYGd#n6{lh2@VIU43Ct(uO$ z)(GnHoBo_GbV^8O-uw`_)9|fX{%9yBPTLMR`wue z0?(6Nvr0e5f9haS-7W}i$fmis=q`=6`V{!qKB3!9YE@@v zTqu!OQZ}Mh$XU-|CW}99Q3m;#B#&rcMf1T5rG!CMt=XzXeES_!04WY3$k-Q_gzUu%rEXv~N<( zNl|>^32!Sb)>Om@>=}F9rH~cXlP)C(!+#3JYJ{A?MX;tutHd)d{9-3@>T#GRm2lD*N|Qoq^3W3CprUFwMUL#FUQr z;qOj7GIcUm#VvzB!;mWE*(S;PO1Q4y_(yw{4adzA8p?>BI?LIcpAKnWOx(Ohs4lL! z`S^n*2JfSz)f2OFYteII3LBuNz3S%k`&{-Rpg8CcO|tE`n|yTFox~jyLD@Oj^W#u6 z6WwG33?7b5-3SDT^mLIXpqu*kGkxLu)8hHw#;9YTZ09+oC`PP;MB*_7>fgX36aHE( zGoc5c-=do_Nh*m?jZQhO=6u!s=9Ej_28S&s5Y>+^6~EJOdb8?UPM4m;Rcae}>npGx z8|ZaCJeO3n2pItHR#_M#O#^{T?+PjlnJcd0`aE(Fwf7sTvyZxmUl0?`Z9_W@SB)|U z6O~{^#eT0&(1#Vn)b}~rmx&>%47X4SdzrhX4BSrFKMt&cs-`kx`I9VbAw5@;J<8N}fZg3hjmmN~!P0z43T6JK+Cw7W@Fk4)z7*UfGw*igq_X?9bmePD0 z3r8%TpF*l98O)kJZgo$!G@XoDbECG={a1uGg1xg8{AdqjeuN>p_G!V{kxnpT_FY~y z;fLQF*(nvsR`hh`%(W#P?;Oe>DlO^{=2gDQR6Jcu-c0$^uAWH5D{rDfO7Zv*RU?RI z)ZZ;!+}>J#M`SeSW2|QvBr9NJd_md``T|`H6~sIStNfXfZdWc?*g)2?7kvcY)oJZE zhpC9$0B$#E1b-3%&L|D7NI~y==1iY9?sNuI&>HbcaY)dsrCN%Rdu}li9^!vfENFP{ zefpPpA*&1PG@U{G`N{MJG%-O@rjH3d2-r8UlDHYc{(M=%;4=(qz#|v7rbippG&y;R zCC>ACQP_wOPkd*>n&bGZjg}1-QtMQpEENRhg`Pb;1s2R(Wr=-`v>iroYncoMuf7qH zQ?%>a%Cm&krwOM)#a?M(8@T)*VmLI>9HwU}rRQC*6C;5xCS(vyg}iAR@)w@RhULjKt-BVgAKw&}d+&A@W9;rogFXi@v7^jH z3Ao7F>oy|B(7WJw{kPxAsiOXGpJ4`cjF|5i_XW0OkY(TQg(Z>&A|F)TrnfuxnxJWq z2r-7ut1Tk*rtsL0K_Qyyj&8+pP8bwJepFB{5&es}77iYBK%k zhdE5$f)|)XVq;XkT|w3{EZS5HN$T-~oshqKPn^z8A94ISnX4>SiEBG=JMZvbx;yJJ z?9|Y2++3PTKm2Al3)td;TsyqJ*KUi97NdBDoBi%3(u}lSe&7@N@meXT2c4+5R?? z`<;@`rmDjIjvr}LbnoxM&vOCcBwn6NQdcMOR^5GlzJ=TrnEY$z@bIzDm%AX{?;*q_sO7#c;D1f)qn-d9t5nup_w?Q$Y6B?9Rnv~kDdL&rA%1d?T~_XyUp z`ug1CI|Nua@}xSIIBblDy4xN3hoHb3?Pt`vqtiDopN52lsL{1(``Aq*KJ+5xRTDs( zR*iL!nDW)4jHQP1*HQW4n>@AWRWh;PbPz~5+GYKKd8^R}_X!z1+wx}16nyB>wg0=` zr%CA6NSh=yK!!BAL}dACo-~UXyjn#MyJmYrUobMnDjH+X$WuG&?E8@X>`k#&@Q*m3 zoW!akK~?4q0`-WVn7C>Sj!0@LTBvW5^vc3gF49Zgk5To8Bqk>bvq~s3eImCPC8l1b zC}?df&&DCHOb1uD2ln6EO+BCgXqi1Q(fM%25%FyG)x1o0D8cjd5oG{tbA6^ax@lhQ zdxf`gyjeKka1S)YY6`kHS^;lILm2Q7Ss}WRb8_@3XxiM|?=7*d?OpDP64wahQ zvqP5yF31b=2tBpq3>5Hi9+`zz#X`2a+T0n1&2GM>YQA?x zR6l~3KPXtfy`8O2y$L_2p`DgoRFDpjg$VaEtr}q9Al z;4p(76?$Yv8H`DDr|GYqK?)2~E45ys1_x}jcT@g8K4k{%W3~am>i5K|35U{p3r(|^ zrKr{KZEPvQ9)X&JA_Q|X&r|z#`K^+Tf3NJ4@<%~+wUXFW!_^I}!#S*QnimA{Kzky) z2)y|PAH6sPa0K$dnXjrJgzo)#@8CP2yKBV`&1 z<=k+S=_?waQ%Ml|F*~R8&sA$Y(?8!Byx=U}TwZV5dHG!4?wqJ^=-dUfPll&`dJbU? z9uR-^AzfDaiMz1yJ`O(LWafO64(=j|Q~cTe4;o}745d^)d;Ig`d*kyZhmBFSVaS1~;MB;&LaKZY!#LwrDyjjeLU_4VP(5$G(g~ZT#1J-#m}%P`mPS{4|U2d zWP^yWCWPR4#xhM~td&eP zubMh!7CG|5%<_hXX4$L{d0SK1SDq32S5`xeI;IhnyU1LQGUv*Yz>#_m{(*B))KY=* z9qaL4o=JgDt$x1~8z>ADBP<>%jp(O~q0KEsq5h^)yxvj9J*$}EMK<{J@hAy(^b`Fq zi%9@!Dup+NzF2o?N6NBOSpRT4+M#ox(U_V#=HFwav z3U2JjNi+w;%m}u)G`g;8s$W?1_vc$d4{!|2h$csMy+x%Oml~CdHA2I2v@UiEG^2*0 zS7}A!$?8rrwQ%GMZi&T0f8h^Hwfpx|#rjXCN)(J)w?W*3AQNo*)4E~5<~eA4vfS~+ z;_jI}ODrbO@{>1-uSx{6&o&@iG14s7GJ8ai1{GfuY2K%wuKup9Y?%|7!rX$cYfodK z-|2^9Rges~xunmJ{s}DE<$w335nYu=9i9L|Rpp82m=23qw?5pQ+9r?d`Ex%xPr`4h z*E5sdr9o@z`}99-iFn?(;gi%*d<6&ot@W1+st^)S;$M--Gm~%a!v${;?G!vh_+E$B zSVjvpyey}VF+23i&rZnra0j+ydwgQrU%gGW#?)9^6QgbNE^(tXw?}kX+FMgcSHs%1 zBbP+un&7O78H>zvR!~*4)HZaWibjZ!a1yBclkCVn%^v`iSaO;s(0{GBQ8v&>QQ&p3e*)`mPf zNB4>7gj^5URm)?rN4C$|M05Soqcg$G1rzf(gHeYjVySxRcKZD_sL)+H;e^Z6g*G*+ z1n;1Ta!DL_2SiwrPaE{sa%rZdJaS||qV_Oi@=cu6tz47+uzH$5sTz*dH~yY6RVLQK zJ=c+{=3dYWimC%YIR?}7O!gX;T9>WYw6!c-oT*T!U8pbK}&oV^Dmiwoq9hfh=%sL@$o(K5H=m1 zR_VO4ezqDUd>tOJnb-C&Rkfwx``LO%)$RAr>H;l`@>*UN-1 zdjA-%=}Oiao|C6QkI9VLuPbbU%N#>lSYn9rzWO=bf6r#GH+A!qcz-T&;P4E#g6>`k zI+mV)Hf<$KfOF<~LutIms0U+#EUD`7j`EAgaALP!-*px&(sl$Eg_(IjD|Rln1iIDa zZEWc$iiz7{uw3K`!yCQ)0oI?}a&S;Zi6uhGPT?e4ulnc=>OW6^_eQ~w6V>nM{RsNH z-r~J+C&-APtAxuF$>wx)a&+1n%d48U>|iwL{PFjwV{rkEE%Guom0J1lsCa37-n&ez zQ8hye#jiVDR~WD{ATSpYF%T1dXp&&$!XV_rTlHZy0VxKB#>nL@HPW0r2aqo?x$wu& z>J$AcB?8{79WJg_vymN-4P8$=`SNS-`;Q^nZ%>bd69R5;F98!-uZz4BSsl+~6N78s zcel2Do=u#0XZjJ{%|Gmg{s?_S=0t`r_#_K51w|>bVJjfW7K8uep3MrbyIL6}_$xRz z1uK|uu|N^%4KsL7v?hr6pUCd}8@ff52MOmi@X13kKRGPWn;M932`?}&Wjwl&lZUu| za`Qll8CYLEehlS7DANX<$pyrz6`pvCA0!_gqVu94pKn5h3AB<__ z66U4ti3@a60teWg4dm%pME9Ua(h|%|G7mrSI^2G8j376P&`cw{FfUq}9H351V3X!} zLPVJ;53*lLLBx~2Qn7!eGbe%Okjr9+8h|7Oih-kHgHYu5+sA*i29^5cnUszW^$m{9 zoH!D6=$kBF$^eLlnvYC(?UfmrV*yWewh=TaCX?HX*r#q(s2{t`Afwo_lW*5@EJ)84*(LksBE`v%g;1Y?BLnLHna z;uGZ~47go{@WF_-5KaKK${H9AHlL0&#G78MmfBm0pUJN4O?^^g7kV@9k8=wu?X)-P zoFL(N4yc@fo+WW?KTAaT<9ecEC{RkK^658nDB>RmnRoHne(1#^V;;U~c@QZ*)tcuM zh9G-PEKqyQATHytbzhnwJQ%72g%8|6b+bU_R(%7Z`|&20sRj1h)Xfu2@8Sy%ULDp` z&Bz3zjs2-D60{oPsWVLj%$ueRUG2;#Hx3!6djeHr)&g)CP7y5Kj+C0`m#T zS+*ev4K|$#2Dn0Bb;fbF0mupYOD^T2!x<--b0wai3?h9uMsTL#>j2D)hNY#{gZ2;1 z;9Z}tNYI0COvgZ1X%!s;V2n+ROMV1|L<*aipsM52$2YT|%qWDg#JcgFKQYPU`_b8h zIQ$0HAV&1t)v*mgxEhJ8oJScRtwP~Dqt#Sh)pv;C5s}4 zKnD+IO@gO;p%4B38zYs{rYx8WY&Lr{v6oP$^u!6ruN#oj=(U0+4gZ@vv86O=NeN}x z8vMhGP>r}7T+&v<@yPCq$`lY?<~k^+euxG8`>Zs;CKVC z1&=XUG|xjr%|VN!rHfrRb6nG!^C7S9U((9#;0H1}q&$NlwCPC`y4v`7S1Ln$v~jI4v{2ht z>i1NIIu>;u$0zTRB@ntr8~NFR+S5i-rR^Kg40{34lZa>CSbXe-Gkjw)D^J8EQ7c}= zBrz*NL^Uxh5yVDOD{#bwCh%pufVj}p=C=Bwc8^9fC%Xb4VvhWPG_A!5#3NF`qeV%d z5?21bcf&xYlxSqMy6Cw2HC&?zp|!p>Jqf%bw_3a}x2=Z3R-a8n#|G>)f14V)QPdV<)O-1M zlxYT?4Ckr}1CYpV9~P+^Zpb{T8lFCu7@fR+vM8N=hzZK*^fOLu?Hvh9mc=Rw26uhw z$d8$wg~<2YRQyoYuFYJnMaW$NYD3P1&6-eM#WPmYBuHPb7efQlw`$tj@4xpRetT_e8YEO+SdiCPf}pob#`jfe81OLtpkWE_;!5AR0{PKve=Eqt`mbF@)0*#np9^UrUT2@__uyVtdgWM9uAE zirOLkaS~%f3G$!H!8>-MC9;bAM zg9p~JefKp6=8oD9#!|p)9ZgBhg06W@6KKdCKA1J@*XN$JybuBod7P74WF7vPi-S_a zh<2ig_Vwao__3&nQtMp3D1X~5#!~gvelGl2Nxz1yMQc&FHDV4^Ik5VSSeyye!Dd55qmahhjA4`rXza+aED!X}6Fvb=YI-8f} zLoC2ifqi=vWold#nO;{LSWb9btjd;`hG!EOOPlVwOzWv+wM|Ixumk5#(#dD9SOR>p>YrqW7$seM zW98(x^8}h-k8F8s^JdoVorda8cxZd?GBiz|I?>8xvw64&6bq=hNKNp^MyR7CAtl-x z!pZXFK_NpsAwx5^6|mB=xJaGxZ_63CN!5~{#Mb@QlAb6e)DoX0vakVnFshv4)z6kT z`fHiY`!;#+-*VmUPz~Lu+^aGZ=R;1p0sGJzj6RgGZDa#WFbQMSkVG2_JJC2U(FN(v z4;`m1AoI;Mwqtt&-Wk1^(rv>T(&BfiFR^4B>jObL`dJ5fx#h0F@zcS$k+ zJ(llTm6?$h&wL1u`KJc|k?SjS`HR;y_!LoJ=*fnx@XbCGsTzw1v%WhmPKU0JU z<0jM#@S0zoqq9fNkEgb=>QJw;s?tst+ zw;amfd-U6KBpOmRX^)?2?Txc>YeD(tbihVYNt6C`&Rw5nzDp%hGBU~xNsmk$J+4%A zn`UTgy0@J6Eb;H}M&&ze!nuefO%~BXK7UD?OrnEqqnG(Ek(6Ylo-|1aT`&0qTuaoq zCkF;NA<8W}Zq@~@%d{BFqNHnYh9qhOqfAPk(KNr9XnO1@YktAd^vEcsBk{&i`t(Fc z?huu)1|-1KF|=5TpfB>@b-~XAP(TKk3ZFW9dOj`SplLx|`V_Mkedc9eXs&DaEEhHF z<*$))?=o|BMZL<#&02`yi#ArvVlATzXqDh( zEI6pG&$yR#gPtGe(YBM1rE8@!WjCqb$*+VlpHbDSe1Ng6Ry8|5GY>?#|6Q*4snTo4 znk2GSM21lyQ#VCDdpye%H+Lgzp_4EB7TkTjobpo=e`{V$9_`=SgIN3?>G7ylWpb*s zu*6+=@?v0ICfIEJJn!`-2Nv}=Pcz&Bvl*Ff#gYsw{}*R(;TG2qGzw#jEXCd3t+=}t zDDK4_3Pp;$TX8AwZpGc*-Q8K--S7V1d*AO5_@3uvlF3XaGnwo;IXT$`qkQc48}k8g z9X+eo{Sm&{pGujr5b4waO!o8J^r_(m^Ad+lKITxQw0g|>-2>L-XlkEy{EBPan_ykVK4{%J>`uDy0Rj9 z70pb5C%(~)U=5Xf+C44HRO*Hb>?<))lKLh9{U84vYP#_z_~gfnqBoG0!}H_j2w>NifU;m1V|``@{vOe|WhGI-|X z^UorUXLy|lE#?&lR+5Tqmm7;~Q)It=^`$54c`FTqxh+OVAJ6{ZjB7D2ASec>RvAnu z2h+pAbaF8L&wmB}U&f(iP%^=iI+=c`fg?kB*=BsNilb?AyeR5gFyqN0=Tgk6+)}fT zsbFMCY_BCy*|$=M^m0sg_Oji}#hJZ1b}>pBmq_~>?Nz`y>x$Yn?GMe3w94TKz>kYQkc}sKcRMNGmVnB}2?XSia9f zn*o~2W4YK#R{Nzs|C&>gg$EfZ@Hbh?ohaD5C1$sJgWz#sC&7~T1{1u4hU??;n6y9+ zi*&~8E3t&~H_^l(Oc-CBmOYBBYJv?FTNf{q42OEYTAS8ei~>(Gxte%+y{RJRmHGpd zczGX^<9I&(;MOEQJM`OZZmBbQW27XcScq0v87BKlXIT6;0d0(`_D9t z^fo)5(&@_W3gB&VT3OFzhFlqp9LJm@xqH$;zT z&T&3-#nFp1>zTfhFTfy55!wQZ2QdS2Ti`1}H=#Y5Uqp8y1jv63QWv~mqCHYGaW~So zIfMa36I3(OI>I({HEOr_wkQOO{{Tdi-(Ls~=w{do$SJ5>0Y5etzx(d?FFE53h7@1Z zArn`i8lmejL{ibd?wCggM*tECP2x?4AO#?kpxuC9%;^Rh2Yx~dKxe_lB7_4l&`rdQ zH6d*v(joU(OnT~u+#a+ryR8kEjMnKVHLtJ112hcC3>a8d97QNq$P6f0cg$=Q(lN9t zC`bPmRFTvQWr1&%ZbE)+$^H!fE%+j-rn&;d%}-JgX0~k3qNDxV&2H5f4qU(5@kOwi ziETo7_<4y7zGo~%!Xbh`yS$okHNMHHc)qdU~i#r`9_fR(fm~WMBc$D-nFp1 z26|CH51rj{dQoHCq?%n@xm7;jDei$*?XJt*4w;C-?1Mj2P)It zpIjbK8WtZbt32;Rw?2l1-#$>fuA@l2FNb`e^GUpJHb~x&+Jv9?Nxb$F6g!`fhq;BH zGq>LN(?9nQDYo9EKT#r~q>v&Z2fq|R&_VC{Rrn)xC;E?jUQPLA;SP7gZOxj|K~pl z^=b6r>}Qc6Xf|N%qyIf_D>@BdcwXsc*29Y^L)p*5i^x4f=oWr4+>d|U?y}sczwCK* zTA}wFTZGVUMBb*40_UC*zCuhoKx`9+LQKwhq4?x@^7^2BBrAA8Z5I?nD6B%+N3%m{ zws@g@sL*tPWm-efbU@liuRU(l@a-cT{+YrI+JlwCgY^se`inFeDu=WJH$?>U8%EG# z^b}hp6KO>6#X+46rXL+99yBkepB3gDtho{dBfYO;ciznu%uNtqQ`hhpD8}6Fhh$|4 z4W;R7C}1}|kk1kcU`PZ&S`&e+{`&6i92

i?J*v+UKGK;i5cUO%4=62nOs}paJ~= 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

~F!bIqA zQXt-d(tHyvMoe~4XD$8Am@^c3W&YIkz>$Pu|9f5?er1*?O?Cy-L_x#2vlnh)rP;r}05HJ2uvSizinUK%d&O9CjKHD**p_KanhS zjtw>W;^QG*uX=zNoZE{{29Y*t{?Dhe_%BaMK~xz1#+N^CWMf#EqCa?1b9ZY0!q*26 z-xI1Vt|ko)%A$%m|1KU;0UMf1*__%oip(tewC_|uhSx_tiblHsb43# zvlFJ7+MeK2af#(Vq)_MZfqJ@dU&i-Vp5*bQSUpQ0T$kgx^$BWPukn@?XLy(d(PS6)0tMIvF(q|RB{;K3HG3EP#qF{joe z15g56urxeLdQa-~HBu{!9(j)J=KDASY=N3pTK{|E%eV2TGT4H-VfvKo(sY6nxihIW zIrlwg(tDhzn%Gz2r##pnKYC>b8Rgx$U`Iu6ypiTU3S)^m5i#+K4ay|dr!b5r(x+K} zmV1MY2%qjiQjPn}3E@g_>T&lTM%6Sm1RS6EfKOjlpn zDQ?)AeWIXuy;v&v8l8KOi~IhSvnx`I01;^%Ek8YcA~*5*P5DszdIRyM%IRETcX!lv zR$|#|Nh{WIQ9-qCOLg=|DF*(#_jsH$f8KVR+*Cf;@2@LGbFC?bm1mL8l#M1j3tf&m z<55nx&dx($zx)%;CGC0tnmm+Ukl(ITYPP&-(A8#KD9qKtBIaPlprCohxVn=PFF;6u zx5~0&kIUVihcBAma)H5XV9{V-rPEx$yK1{uYCbn1hUX3GS3epaoy(hf2iYUIgUXhV zo9pv0aYBR?8nQ^KJ={_!I>fZ2wFVJ`OnqE+bzxF|?R=9VUwMQXdyd;mx)0C2D$$ zpDksOHi@f~mOI<6z4>W*t<L@3Q#2>H6*ZdX!n&%hYr9 zwso$(Yc%By#YdIQ-)?D#U#_ht(5OP{n{UJ_4D|VzoP`ABU8bE%Jn>dp-I{zCyx+O& zUY1`Uc)&Yeye<_ixhQu3xg5E2<>PIva8%@d9slh^e*J-YH2U^uw#nVYjrSAM#c2oT zzuWIhrdZI7sg^F|`QG)MhANCfk4aB$qS&(Xyk0x)rEDGZfHN4I*3WypUqpT#-;@)Z zj3K~N?EbrbbZPFGTQUUg82hIG)BUvj4PtZUqs8K3RD{b@zV4jwJR=oPqPwO(l!tB3)!zDWkE=acc8MN=>k?BMTm5Luhl_DL$64f2KBR zvAYqUdM3p7VwpWZMJoyfwSf!%rsxl;Xf(Ls$A9u~lV7kZc_JI_$>~M9CoX4~P(gq6O2xUW zbC9nBo5QBN+wb`0T+g~U$h+RhnlQITULI0WSD-Qg<9t9&r&tg29Y*W0U@9a}4ouq#)& zl&p2T#Qs^W8`2@c6V#m@D_DMpt>jQeALXC~FKM3JykNd87Y-}JT7UT-xg%Q-&rhJ0 zT6vRMesu!rh!V!iag8vV;`NVi)?KM04}2_*xM$V;5r|8B8J>YDS#cwt^1Fc~GKYC5 zx%I^U&s+Bpz4myd)L^xE9ZKTCyy0q zxI8fbt*M9Q9w~+5-)mJzS`#@&{(Z(l!J6d;Yrt=Z)Zwg0g&#QG;?j}Rf#0$tA!Wc- z;k}bFhS_N&c>NrvVdQ{gg-x;P#}~rDMX$$FiTr&w?$KS}Qc5R!t&ijQ#Ji39&pYgV zI!f_3#dTP)!1RV0`u9#S9^x7oON+E{{Vf6X(vA(i9jfhl9XfKg$9-QDp<@3f1C09! z-zv4=bj!KeT`zvwP9McdYEqj#RE5V_iSOvvWgIh<>-ppxMDBS)z9F`Tk)d`nzr~hs zlbtqgE;Hgy*7&2DUML+r)yO2^rjidLGGTUpdi;9mI&4uwO(Ff%Z3T-d zxI1>hD0-DnQc;aK;hRk3wYu#H3M)^)R;7?p+>#l z$d6YVEC-;Wto6FxBh@j^)n!{i6W`b-81x0`qFgch9juU$&Al;boDC2}Su=FA@ErPs z+tGD|{#=~VFL0TJ8ov@stgAT?Rt(ob+Wn0ZW<3NV1z@4DQnh$y7z_tEpkU*62pyGU z<}*rb$9#u+bf_1JsV1`%>4dpMh_q=m7MTzD40jUU%n`Vq3OUxaZOfpPYqFX^KWyd= zlel6Gv5UZB+!jY0j0Sk1lxquPXSEBhPTqkv0=IbM@@ZV3XO3O2nT0J}JtJyL51s%V7DXp9BTn z)pH<&O=+txAbeTRRU0SR`35_75lhdtdEK=X(W38L%(qI(ttjvd)UE5T=~Rx|ozu2< zP3($D7JU{VdI$({GExbqRjVjtk5(2Y%6CaTl!FMXKcf7bS{k3sbOL>s z!y4EngPnV!L49C(9HgbqAj@-7!KdQZYY3F$(q_cej%LKqCkgt3oh4U>F&fyLu*upz2HEK}0^vLS1(^~R6BFsT<%cK^CA=gf zDRvoAJNz}I*dO)}z#4f8Fy?pvoJhx+$rD|05SHc}oX}}Z&)e5cH(+q(kN z$0d0D($^(?`cASHN&NlyDZH&CXvGIONaxd}O6V zcjt!p>jf$EyPd3eIMe}(bUz?YYs9k4Fk*kRQTB6S=~KNTtrO)6Z^kg1-GTG#H!Wo> zC|@xdM4KdeQ;1e(_Kj`bwJT68qyRdFpdRg2%*pRuU4sjaKOWSwB@5)nSs{4C`5NfFf zhIw>1Q)BAlLL1A{LoFCSCbO4@oU__UU^w-TC*w`3T~xZ%Pot6B{4g`uYhy;X=%e0F z9(SztP!AYsCM^A3YhZHzST##@!9^HgJGH2R=iI#lpJ6TMZEE!PlecN0;*7Jl4T=Q= zujY`=H1?z#-#1&ET88|xt9Q!_-U?^w^HNQLm3>C42B%Nuz)N<<{*%%G7xBj1 zDuqyN|8xx{c*hR{IQ?Ol&SbPR>_9OFQQ!fE7f_%81q)ELz8DU3WTmC=OfQCa%EcWH z3o}rZhj-f0Vx=t%;9(;f4ofjm9ENxLz?}U2yfeLlbU2KtGhG95r{@DJ?U#UUE%Yx| z)Eec}28}aQHO_3z9>>_ipL2npZ?9mat3oNS86*VuMKbWyuA@>1rILd6{X3DSr_)} zq{0|xBlb{t8czN8FNqkf~eFBaT!mi9+CRy=2gdN7_McKB=wtoH+f{k zp8^N2A6K1~np=0q1rqgdN)g_l^N3WW--a7>1}r_A2mHx|HOIo zpcBV%7gOG_FSg#0m;fhmN3%azE%PHzv+O70) z2(4xc=x3xhD||a@PjK5y8{Vm_Io6GQoOVOSNiMuQe$5mJ>b0I4`hqrPU)TBjSL}}% ze=G&&mxb83Un84Y>CXDj4A)}+8HF&-vV^pnL2D2DcUG4z zZ-$f=^3~hToB_GFx7 zmFrx0o7B`wj?I0S#TJtC#MgUrDrAa^^2BKLxNKwhC5T<@Vn=#yG3$2NOj=C_aeV6f zc@i}?ZwC2Ng9g}Vt0Bz zH8mqbnaA5=nKq9jUM$s~X}+|pL(TCvj~-qu?HQJ5cAK6l>O1bGCt> ztPOKE`NE;#$i==qu*fkmKkPzl$8J|$Ixz1}fYsC=X#?G^QJadIh;r(bcDo) zs%9vXZe%Bu3)|9(@``oD+eUn9!)R0nMsO>=S+7RI&7We8Os9z{kLAmjZ_Z9m|SWjjsG6^GSzK(ZXRs;93Lo}wA z%uHkjMo@Wum8SMHZUQ5nc6Mko?0XbTTzQQS?PuuQTC2T+` z_IqPkDhDBO#AoN6YE~04f>7%QCAHSjFTGBN0%pF!edLx=uFp2?Y=R>mK6N^qDTLF; zwbsrSArcu1AVG0uS`(9g{H|SM(+NH_-HJP|UWX`(d!db4=O=b*6HpBI3>Q_aBZ>OK z^J_*joC8V-Q?@U7|KuvkgkNp21k<*&9b-UiAuCLkTo1LbDV-raW~h`6J;`s6*hA2C3{mkK2JvZ5V2#{6 z;v)>9mDoa_@sHrcpP(A;W!01FsHD}?=qRNHI+quYi15r#?qp{$Se(z8;0^BLr!X z$hoXw3z-6+DwOpNV+(pripV*)V+|SBH$_bZo)k|sMtk?#5#)#J1Y*;xrl=xMI7a5T zd=cbEE5=R&*2~TgJk~2NPHexQhVhM_t&lj8|9%?8M?WDn;_h%G=!VuaLq%{8WFubl zj97x5wVska1*OaE6wD@vnI-nG4)S~+Cm55eX5@QFm`%#&gZOj? z@8~!rx-`Yb*s0QmIC?U}UUx!&|xm!P^)@U_SdxDENnP-s%bn zG#9`BA&AyM2x(>zg6aZN3O0q5 zRPxLP^njZR>4Ax35fHRQiXnt?YY0Ih=l>?4j6jOirq*+(wBU-nCB8yR3Ri^?@CyF+ zAp5t61T9F0wHioCofeQD0)A?_6?elGK?o>{e+lV-2{eBR@>&cpTpE)964)Sw`B~M5 z;cjoH;358{+1K@usndU^dl&WJO#8uLQhZV0ME7{ReEh1w`x8(YnP1df!b@Ld@}7Ak zp6R7_t%Q;tOQi7LzmZPny+@&v&5*!eF+UcIxvr|ogm|FuKR(fK?yY9x2wz|EKyPjB z!o|etV+u?9s6ZjzQbTWTI>AWyv7lt@d+I?gFQ~)DRInhD>9*7RS$nHM4cu`;Z~dBu zi}}Walr3~G2kmUOiYd(8&gl2fM*<2wk{X)otR6O|uL7AYvYpA#=XV9{c%-)!l*eN# z)Z{}mO#j&|+<0&+xgY6oPnf-ql^1@@Z7fi_55iDQH#tzwrl`Bs z(+J`E$*w&2llGy2ifmzl3VV15g?yU=1$*WM?X1y`DeQX*%202|7Ph;zf_7GG$I_@z zU93!9tVvz0N?oi=wbB}?E!}Yif68{QN_Q62obDx^!u0YQ#j_@KD2zFh_THq*1x4;) zV*%BaIWwC9M-o0qsvIi82=xcx*)XB=VGgK205xZ-Tm(?}0&gVXJ!#Gy`#M!FA3#mY z@8YAXaKiy^Aj*dkX-1Vd*m4}DJu_uJT%|o+ zWj%GJJ#}S0scIjX7`~G*eAi|8F2L};E^)3cR-dRJXFU%jw4>F?`jF@cL za*~lfanB?_42V#E--_9viDS!aG+r`i=CS5TB4$gKb3AkQ2dXX5`D;UtBzeLS>bH_k z12q5|aU=}`=mcQPgiaM$Dn2 z)(i=Hi`P7fuT#=x(?%&0*#Ne36i8$TM?2o|cb&!L?>Q``6Xnff>4E({+eexAk$S3} zc}Y`26LOZ5&bVK#B!$1eqRgJE!BV#DMWo}jf=x(ehmjp2f-WcrVv7%5fE(eb8V=m3 zfm_u+qbh@>%GdWlHS-%}KMOBQGfr1xYu95hR$?#KU@ul-FLGr_e6!%31!w8^)WzD= z#p+Zm?U6&Gkpj=iok?#=r~Q}-ou*ZDW)iMcIjM-9Nw8)6V9PFIVl{Zd*+j|)BkSZ3 z4PE|&p4ApMK@k)XFDbmAfThf91Dh~P*qZMo$+F)vA=hMZ5yuRm9Dr2-T5iRb6L{n| z%7%61$4cF$#!7u0?57m4*^13%JChzG^vK_h;GaB~B;7v%LIebkGWsGHxDE7Ewpwq+ z?gUW5lmIm+Wwi5hKjm`enMVEDT;3CDV&)h1G5A$#{u&^m2csPIH9+|{1uqxlYyjejp?utWTm_Y!Ucp42(YI> zbZ-W-8g1i*oVuxeO*?^102!e2g%_Q1XRQ7(6{E>%*#8Vx+t zjv<p_;OutH_?JR>=?Zf|TE_`=uEd!G(-Q z6lOAEX2t&{l5oTXA!4UWr%B$R-<+9)^SXVwN*H2~iOI0CrfBF*4P9nu4#6$wU6 zzz2D%*g<$e{+$lOub4A?E(7T{XKn^ptS)e625w2fjRClUD8ExDbm~9_HX0*k+9P1J z(HgPQ9I??J;Vj$HQgh-{b81#|@>FxODQWc1Yt*Tj&?z4?XU2P-I&?`ivfXfzBisWZ zXX+3d{>V0bT$ z2Y{XC%<90J2%rgoKL7;QX#k%CK3Jk9P9O;rI<^bu%x}OwDGiuM2u8MdK#Ex)Z-6Tb za0TnN6bIbq&6(@L{aYKjl>xV3z!WmQ)HJ;W#=_~PFVn>8952lOzECve=5&-#FJ(^a zELz%vi*MD6Bk33{Y-q&J*&j)#+sX+YK@cjMaAX_o>RBLAk>@pffh~LqHVFf)2Dntu z@_-uLLLa~_R1oZr67UAM&|7c|6$Isu0oeE{M;L63K5Oa_UgXXhHHZPK)MrZ_QU?(9 z_YgFp;{#q{e8CP2nQ$cSYJG(PyLt4g zwnUfA%UP{~nk|(f0;qd|dJ}kqipecF)TIeVa3%qa0MLp(vEbatwc% zmsf%)8o+HTat9f$q|v3M5sV5YjZ7ttHYJUP(>g!TUzd2JG zu0UcjNv9t^d5z%py&TdDI72@_11gcc#tm@qWi#baj{tc?ftnJiRe&0t(+;5fWUwGK zKn<$>umB(+JK~u5gMDEG;Tb`ppvWI_6U0f`i8~FjWgP0IV4XmJKafC>{0W^?us05a z=H-Y_H_NGrs9U~vVI42gu_;(T1?D3XZ;(;LgmX)WcTl3greF;VEJf7WAftszR*864pVFK1gx$Iq);$@J2R$7u=i`MP+y(64uR+&U|ThWmE3n&IClp zO#`i{}o|fv^Lq2I2(7nXFMX44o=C zaZlJW7@I`8TsimIz<<>0bQAtt$keRhi7hkQB3x)44`g^ zr{hSIs7Du&$^R6QU-@6s{sbNWyS9Hb|2pX*C;Yqg|7!kQIuSHzEdbcHAg!SQx`oIn zj=2ak77wChxiG?C8Ib*ZRJ~J!tR^y~?;OxKJ*4kousFry)|2YFYWRuJ2gr#cK*)h4 zyFffCO2_@DcySsf~vK`1=lV~D36_(n*uw@24Bnl+L zH!+Z|*V^daW?gPccb!%Vr9Vt0JhKm@vIt}m0YSZZ5;QZN03s|jiEePVC8cR8?YU+$i>Wmud zR#Q1%6FFWRIbI_rbv&hd)4J4 zbuZ4Hbs2(4n~xS|F9{wsrfAX1{9&g?kTQMhqexJ-7E8Fj*1>zMQLENhU14SQ65Q1; zazSr@Hc@uV9yQslQM=JfaPyT}Z4U_B>Y!}F7iE!Prl8c^UkWpQl=8wDd{cswUuAE?85j@(>R2Xl> z4>=VYhvON&IlN;VLI15OS$CTMZyR{WT7v#zQ!29KX2ZwDU+*#5s+pW53GT?T|&Mf)1m9EHDDMo zs)6yAWylU!%T}_0OPEBaE5O+k(Skm#fmyJCtN95W%luNcpgC(Huk7ZKN1_FtQ3vxMJZ`~|{Zmc2 zNUOG^)f? zMe4-95mrpsq{}w2f_yWGKvPTK)3yk?_A;KKori&?SjO~&H^n7ev!{nGb zmk1@R%4FezO}yma#WbEdHwn$I&fF#0P!-d6K$Jc3=p>ge+buyiteH8_72BinY}>)? zwGz;GL)0}JlS5yBtk`8C2}tm4TgR+fIm#YTK z?4if7S!iwL?e9ult}K$7uK!rl#?Y+{&*u%9tOfZ>gGc_WQ+P6P!h$$`08SE+NS`2) z*g&MeIeI{jf1F^nZ>sp{c`xutL_!~{7U%=e|Cp$IIq$KwZn=s@KYm-_4 zQGFr79KH_k;-4~A+*fIt>d9M<-J9rOk3Cu6P$z7v%?6+G&(vVH9Q*i)BCfb~Y1)lJ zz)T{_iW@%%1?(}OZF6qdeN>)p31M_CJ;0=;h@JO}T%w{UZSl?uh%49GN99<+c^e%A z810Zd?~jfPgV(&TjjAd{+*ki#g|2yLSzjYBlM!NzZe(2j2#WFO)2h4FYLl2r(-nS^mNBx}MHn0UuoZ*( zuRgf(@g&&s;Uu{6es$RK9(B0!4kg5FkE|EMn}XDSh2C0FX!j@3+eJ^{sjR*QF}hY= ztS>^2&lyIA%^7m_KI;?RbRwsHj?zUUultG?E|Zk+WP7vfrd4Z=XNxza5Et*Xa_#Z6 zePl8sMlC~_BvaLp94qeG5dk~8GYs=~CGT(SGIWh_y}2)MzBle&=Rp-(Q#J$yyXr^; z5be>LY>#G|rpB(Ijs1CRBil%ogiVn_5;0eZp8Q$l^CyPzUit~szVz{L&?#G}R(1i$ zLye*K94Tcq!S7`DRd)==7<;;SDY+Uq*?tGhox(lW$VmH?r=o*hk2qkHXwo`<=11>H zLQgwe-Sz%62CCmj&1lS6zjnPPmc4W1M&w;~!YD&-KSd&Ss4Q4}f+NQ~d(V;Jb>V$( z9o2;kEuu0KitnXAZ8s1+1swTbtxz?7{_LF4PFZs)@Sf+bp@mC$>)M7RfxlImPuZu6 zI<#V(c7$jBZE(L_i_W(xNphCpZg#6HtF2T;Frq#^(t5Z&WHj+U#^0ol5?rHX#HuKP zbq-#=XDCNPA|gLQr>B9%#+INEjAJaYnvX2dT~HL?YWJ+)XQe52U^zS*{WHolzfCvh z*n39;PvSm29w^G>KbqbQr6$r(#dxi4GLmV!5oTu`-BTcl%_e_KSl}OwHi?*k&itOU z@$^zrY3}ec)W=dc_-*hEnRT06!vKtZe1If*B&|_i>EfB+j5GOc+|Sb>|KCYwGsV*K&*Mws3FiDFbMuJLbU%n{?T z=7~SnkxNE84Q5{z-`l1A`0nlIb3qC(o%2m{;`!5S+mXv% zy^%}b49ml`jnv1X6kFQx_7|QAKX|R)KA*2!y~Xit`6820zX{%V^9OXl5=I{Pz_k5_ zL^5e2&Am(#!){M+*;A~NCmBh|X=yraE|~|9Mp*kxZMnk9Cs5zB>=DE4_(*ZD#XY_8 zZPKLk^~yo(tWm|X_g;UxS$R&5iQBVzgJEo@98YJIwa7B+ShaS|8OOsiHu2rVUw`}r9^vC*w~?!)Y`p4~{cFuN zs@F`?Rdn)j#y+Wxh*hDZ%4k%+!p^4&6E!pra$K>6P!Qb zrgwBA_4#saZEi1rZrjr0P{C$#IKBYOSKKLlKc(MGlL}Kzt=RZ5o{6yew+pd!{EXl z>=l!Tl*Kb;o33LunZxWBm?rFxF>;S!O|rel!}NH4wk+)cjqn-s!5P8RmVzCg zlGaximnTVnFL}9P`%}-8fN)ZZnU6{k6|Z=vhc@Q4Nir0hNV z8H35*^5---vlXV$_4VDSp}#vZ4(HxY!XNYT=Y?y<`qdRwS3w*HlnCT?2P-Qt9mJnGCb_9aPbj-XC~6K9oasgPOO$KDC`58?T(OIex- z4t!R6@$Ur&7hs;|aaLvm;0p&mzG%$gO}-lOfJ;G-`d;!bz*yvW(Lk(yZa>p&!YRS7 z7FLpMXhrs~L(fk#5{7f=2n_!);l&8N_@H5b`?b~lYE5Z!D5ufRB+0CkC6j}8+wK*S z-i3o6lf-1Y)YoRZFYjb|(S+V8M@q|~e7}l9zhcp(^||x8u~QK8eySfRI2Di!Y%U9= zjY`jcL%Y}Ruy}J+&?zAI{Au~%dLVa|&mt`wDJa#(@p@9+qEuRifrmoq`H3UNy3$Bk z3zfyd=Xw7y+1hJ_;t>l?GE&XV19OWMZnm+ybnBPs#>m_%=E0X%S&wc6SX=Qa><#VW=sgrtB6=!?r=wlOSc|!fd`p)ra zeajZU%s5b%E64bJuq646zN96JuHxrlTFe>E*OpgwjSX=_uS!%*IP+T280_8-HfNtP z=d=tl+$P73(OY4eFqO4nygH5_ybm}dFZb$y6WUDny)Zt@uq zN+%-hPkUb2qImlzD6w%!s?#_RJUzY#IYyw}|OVV%)InR6!{A z!n{*u6RU#88Ld;uT=TS zCvwC-1DzErH=NIux=%RZ_Q~>}$BcH{vE4ACj8U90qK%22@FR_(#Ds~_Ahb)ZU!%4l zc89-cLG2oXZ5bBhf}dw>`P@;$(SqK2K-I$Bk;dIJAi|8?h*}zA$O&f`c)|s3cYeYQ ze_Rz4Ei8y;MXn*&r3Jmxx9*KDD84QRk8DVnzh;CfNV8@{D)?jVEv}%{niF9lIVugC zM#oPF{)}#R75@0nYD50CE@ezHB-HIw9n>)s2kHO@3;vf~=Q8~I9rncCJS~_%PzYM^ zd&6I~yy%fbYDwsmMy1qjA?_TqXu?KXdrN6>=XYxSyywC4b+C9{u+ zttIw{2CR`q3qe@HdvFV&8B#BX^Xs6V$G zpUkp8rgjyHc%^m}dC2#%X8c&CxX~ue3O>=q&cZqIAkNxlef~op-WOx@_>~t{4<3?N zY+n+aSIQ4-*lG(eoGx`4ulF6Z4qh4EZ&bYEJ9Y4#Ug@B1Si-2E-*AQ4Ro^fN98V@= z7z&2n!j%7TUyu69889bt!s5NS9iA zt)y=&%%+L3E+w@hgQql9BXOqE94<{2TXEjonM^GjUb@OgB22rBM($4Yv0W;vwGycu z7fySUU+Ss0sw+Qt;Ea8HEH!;ka4F&r{*x%lX`qmWAzG6(?`w&S_el`V#s|m6*32c1 zLf7FxO9xt}?Y39<@#^;(+J*QRru}`Sq3&O(wK1;dF0EF54i~D`eUIkN_=(O1dcExI zntcuz&2-u)%Dv(BvixUf7c2Dd?~)j*U$@p>)_PZ0xEtx)P99&?v#`Ja6>gWqXH>@V zsf(ZXT+(LmkhGfbJ{s+6;xlW`NOe2X&{OFoTI;s2(9czpsJE@&QEw5h4znqrc#R^! zbA?H)m%sb0nl`PPH9eXBY3qYsH3~RpTRXJ5XiM9;NX|U`{S-AI8E@_TBH0@I{?;h+ zkGoagzl#rg2BJ292~z^J#&@?O2mnATP1I;}^tr6cwe zE2F!US9XaRK`up8(yp>yLhc`9@pj!Uy`{d&FZ$xYbh#qs6XYNDGp@7vMNt-MJgfN9 zigvHH&E%%h_<5&7Y1vjKv<>@6Wji)nXj@<5l8tdR+ma@Xt}R{Ob)r~IdR%y38diw1 z#8kJ8t4cm!Ec3=r3-+oe7t?7~yxC7h3>%Bq7IP+oZc9q&g#j(ML}}#(lM7?E4!1ZH zZJvSLtGl*I)|^5+9^z%L0iRg#WjW{`4vuq{w5b z!sHl=y+gC1w3s>NjLsde(al!O7AOnE`7@kstW-FV8~#GYWtPtAlNP#c+pk}*>5doP z?{|qU3tc>L+rC^xExv9(QhBbtZOOWVptb+f#U8jbX_rJS5NhI`#cL8#V*{vzoRPGerUW)@aSpzW1oyupv;Qb+s| zD+>hnJU3@Gx$x=9md;a^>K`-Me>geciu##wIJek0 z5;Edd-ZS9}SjcW6o_;slLbtK>M1F^hoAJiQPV)PVpxEZS7kQTI*_(Cs-mOmu&pg|2 zKl+aNO-8T}{T~2=Kz+Z-E{~J*dbemEUBt3gm2_?nmp|YuX2$K+v`sfnd%AQs^)qKZ z0$RM5mn(9JXoc)gm96z1VLh1)h{J~Hb@E-MZ0santFlL!V|8ql4@G{K7{p1_28ONY zAk3(g#wOLMzUbw}Xe(_0Q=QAq%0xe`NnXe8E0wAYU-lBhKTY!ib!UfmlIfJ#%O2fz z%OlWc+ioJ7^GkXh>xZ=T#_x7Ze9eZz5J=*^?v9QUk>YuCO)`yIO5L7F!e1^^+qPx; z>+RIOeVP7xL+#!E;*|X6$%1 z(t2KKtJ|-$dAfUVYu&$h|JQ8?+V|2DQ7eD_fAv7?>Xdas?Rqwxkv;2u(2U4)GS6OW zb8Mrcy~ySzNq?pd5C3=eu15Bolx^Oi-J$td?@8*U@`=g1s9#m>FSgcuXe*|n@zv)Y zn|29|bK7Q>t4N-v``V?|c#+kmzvyymk5&^__12KNEa&pj6)|u*iR{wvtICu%N{O2b zkpa3<-7+Yb2ZC{oST_jlf8M{u49@Pu+%RWsUE7P%BI^{ z3ewi0Rx4)U?x|*8YI^D%iET$%vpiZ#G$3k7WR|LI`&(_&ax<9dZlF#bAK8Ldts5y; zSK|5|cU_EHG1}5Xx+VP+685H;N!cVV`dl=#`-Fc`$8FSd@d8gg+&p_{F9k$?T6=%5 zJ2uT2B2VTyuQ08XX*AQOcrKd2*@>->eU0QLQ-Y5Z7fYWD7=bSl4=?h~+ILOWUpw%e z$P!o?Wd=pFm$zIN?W==kQ$nOr113^ky2p}T6(=(DyeOGx+sk9Ml1}9+Q+bX{yet3F(W2<1h>pjm&>HvhBnOl z_gvFf%TSNA^ajYOi)mq>S~+S=(Eg}2Jb3JT`)Fr%nxgh#r$oFcJDIk6%rmZ)+o=*( zDdSh6mP$q?epMd(Y7>R(hcw;Z6c1_i@Gc>1wYGAsZPGY{sG?R<<3A#4q@McGUgpY% zvscv#oyv&c(5$l2sj3j0;BSClwL{Qqn|>O#PUT3QOk0sTO9(dYt(1}c%9`=zhUJET z1FcnEZ`v>veb2ABlh8;i1awbTDcu-NTcnjDU{fa{MIpH%)`IQDPFXhfzwah|w3}Mu zheV3(+#H{K?w5S=QO44;IwuyKz&vIOB9dsd`;SH)+(m zI<>x5HAr(!d9E$d)x%ZyY}Gkh*|JK1iLe{RO!L(tJ+z@ATG5T%2Tr$srR)kC<8*sU zChX{)asa*XcF=likl!~>?O#a3%i+b|UUo?me%RU<(WuzdtJ#!z`kgCXc7CLvXB za?@CI?Z|cp8SvkComVMzBz&N&6eV#_?m5Tzk?iheY36yG>mIDbDCGtcE)eLPF>OI5 zU{i$4VwNab&|371O>u+34(~?+9I^>V3Fpe9j5uo8P0E!org~;0QnSxB{!r;MT#^d` zk;)3qr>O-dV-P$-X^2=RO3#?(N`{aLWVH61<4yV8x08e6+2zoMDplH)S-4>aVs5PF zliXqqH{7Q54>>R@*AWs?Tq@r5yxg>2uFsVyD2li~kyczEX_lopL!fBcj_2_N{C7z% zBd27Hw?tt4wP}qS6W!Qkg4?wqfIAQ9*MQ+U$am`G6b98fB$pNj0_BvXeu59dHZZMO zai1_j3hzLp&9#KAm9@RSBx)P34i)UcgZEHr$6LyWaoUAqF~EeD0L6}WgWg*)B9`ucM;yzGkD;zHV-9zhR| z_b>Vct#BcH4Cc=Non+cW&7v1mTtoc5Cc`N!jHNCKVXsWG;eW8Xwnc`N?j@VoAXJhV zw1n9T;r&2Tbh?(*EmR>DL$4BPRMmACJeK7XQPAkBZ=A69rqVz~<>>2@>h1=fDbvvl z4zhHknk9)C3w*v8WS&;~Hb+Ub)Oows5G^?FZ3RHP!gm#n`9uR@HfL z-+DdLx}vS4%D;Y8Qq|1DXcsgPhQ`+3MUs!-8L_=Lo6MG$)_+(cKalfDz{n^FDS^K;FX1E}5k8^h?0!0mqk9cQAeKyL-y(Yu50@mUv92`c%-1cQ{* zA-N6s1fBo~EKU=j!a_qq@%qwIOKSGr$myx0aStR{yxyz4Pe| zsmnjTi#Sb2fOKn7(09>|7Y4)qWSmZ6LiS)`1gB}lgDEutL4!iXr*xK320)B>gb6rQ zqBnrgp3jLt@$fHAm{#J)C_o93Asqz~W#n&!-_PJLKrQF9mx(*L=ahAXILRyy#1f#n zapKJ;bP93~Q-)L9Ynd(ip~n~+6KXG`0r3+a6J*vyI&oli0vO5d1Z)W>hw(V@$$gxH zeqo%)h%=aiMWDpUFs3YWfE_PE#Bs_66um?IH(nATPyq9&7cyZIn%&o4$|qol$G|mE zM}AG{HT>D0z2^SpHcqa!!DchxrsJ5r^`>z|wkPm;f*)T;G##yt5^dVA`6P*N87W(Z zu)JLULL&R>t5j^x7b8m7-XNqM()$OCqcaNJgu#DsXzBA3fuC7A0Dl2ZM2wS@)9$bR zll}Aa?&)#=!|9vuDcK>t^5|9#=vnvN0?S6~Kt3EE;+%v1lk< zW5u&(G&QoSdZ^h=AeQzeHB0$kge zhd;pxr)4YwLyU$adlvpEWEZXLOAt`&W_7pU=donlZEqFkKM)EUK-|56^#zSCuKn_5 z0nLjYLk5P)TepJ=qRWSCVJ9buErIuNf6+0Cv?5A}kY44ig2`sK>Wv_w zD=YXCz-LUz8nQzMaU6n~Mo9H8pak)dVv}who5u+UK7>ry>(zbHI4$u7%%0AM< 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

  • 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 literal 388792 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
    v-kTbIIbhJO7chBtK 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%0AjV^teeOcDr@^)j;?EYfj60 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%0AID&K%;+!y{2)xip(2ms4VQQ-V^G+ro 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 z zq5(aaLl}+^@LI(_(Nm8%a?SgE*4^sV_5SG;c!B&8nk3+x+IvL~(I^vn4)X@=!Tm(d z=OgZH)Pct>o7UQ(cD^|NgONK_7`+9BTX?PCk*$nSltj^P%mHp=>{bJq_)Pm|U?ePxPCFa7jJ@kzU8dsAwB+QG2C#^5WG1yz?R z8drvP&E96PMp`s+j)72g7(Nn;;=O|1_lvyxPe$#7@J~j!P24*ebx+Qa(E$iQ84lol zI4o~ge}B9?kmukxrBz*Tqc{|O=U3d7xX>0Dvg-yv71Gef1!EA3yp%LaH4q*F zqakvIKzS@=Ml==Ng`6Q#2u~FECT>0(PwrQfipo@}Q=(x{6hu^MNwIvn)&73w3{v#5*~4%B8P9O$aTrpuB33e`?a`9GD6v*VP{szh*IE@EyFP~ zw$dd->!$?(kQZAobhi_zC$IlH~{I^-d8 zjk1sV^5ETdAjTzq#C$UK-Y$noNlG|rYJMfMVzDEPNx}4y;`fdY@~d-RFxELDj3(S1 zC}i>Zd2bRuk&I1^ua8*T>g##N1rMua5Co>JvIzCywW5t0x;~saPKUF4bSlLjIPT#+lMhrbDcAA9_EJ3ZDntSmaG^mJ zc`l@`KYqcO*i)WBllB~Z`%mTDL)!(Kdb+7ns9SyO zW+od4K7E2e{8ns$&s1sk8t68&YaRT*y6}$_JOM)uPlOdXgukm7WQEeO?a2RFc4tVh z%6q37{b9CF$WSJDG-fM!oY#Cu&XcGM$&YgPIL#ecJ*RMsS{rWZ1+V!f%>n)m_h%^`_ZBd#`klB>%1Yx=}G@8s(rj z`bJl`4{i?-a~PcLuyoAzM0sGi%+|qJojcc)vljYLXdpWIE)6*Tcj9>3n6kZI(du2Uyu{|LfA#xSKa;H z>gM6;X0fU*ZS;n{FWAOn8aK1i7W-%;Y*|ud)<6idt&Pu}V?R;JKD;mLRx)K_V(V7I zMG|$tC}q-%&QmRK2?j5d?{G_O`=EH7sM$inux8SyE9U6qCba0Y4AqwuLFRr zap#TDt&)^k2`2;b5xsCnV&0?}(9ax}<#>J$!o_qB)_kIBtLSLHoRekl*Q@t;eE%}M_uBA@8VoyV#W2)lpA|yE}~2yUi^2WEpC`| zMwEKgv>zQ*dgK(j7$cQIbRthu&QUXyF@7aJH15mi&F#1C?Gn{m`bdrg8Tw?dshgd9 z9FmXl6 zc!*NWb&h^Ys&vFVsmsOi9zTAr8uW}doRo`SUjB=noSeTwtE*;x3QyrL?OI!J8^;xX z=dU zpif4D0!5MbH_(4UzoeMi+w6UprXPZaASCYWIp3bk%sI1j&d$C)vTL=Khc$Qzy%FIs zAQlF~!A6b%HUKfga5Wtd7@dNRj2kq>%QaB+b)x|%s82A3=S*N~5bVItK#d%5muJ~v z<2{f5ggFXsFtIE!m_2pKa3lax6D(q2$}tc+V9po`!e|q03nPxMj8xlj~tF1g#_Bz4? zLNQmmb1}6rhXHc{K@at=*M63k=5u#JLK*X|;W5SM!=ydE|Z9nQqnO16sg*bO5 zzmmGtKCb})*bccxLh9!bI)smGWSWEyyCdYlE-b-PCzWi(9P(R61+qSw2u@6V$Q%

    $7LKWyQh*^R991lu^Ul6mB zFo#SCHlBl}f(kA}MJ=k3dp2^PQ_T&Pd6cQjag8uCvC|{M!Y3-8O5&ZtaJO$ELbeAy zpR&uSz*K|`QnCz|zFMm}*n|odsCrWy!!BH3^)6pLe|q%d^6cZ&UZeJGzw$!{Q2$wkP`fEVV&t$pp!g1=sj9xz8{vp*FrIJ$lnLl# zz++oXmzNry0PIyQ_^A|X?M36gE<`7!)otK{y-Ha5<9V!{ZpmTPlFDq-m@%{IQbiQW z5>ZWrFa?1-(L!N)-1$3l@3QA#-M@K#|K{r|-@jI*|8Ric`bwV6A8Du+S+Kf*H+uDwrfS`Aq&gheD97ymk5G+04Hax+*%$H z;|5cngnQg>t*>pg+8f&*oLuzh%rX7fgG)k(+=u(+GF+-}k6AZzLmGqIYPYuAYnzYO zTdS?du5{FtkW~J0F#KBndB!MqZw`?dA?S`A!Ue%G>AtII_P;H90?ZBghhUAb=!<-= zuzwnalyIBLg9O0bU27Be{Sl(Jq_7KgVp&;9!3%5o8*LxFlg*a0|Mdmf-`4;t1$z}q z{T=rIWgL}DDH=)D%3O3TUCof=r$|~klK%1j_3x73wbar3*FR*8&;6Yj@XeopN(LVq zaFNO2S6)&=riq^N}ZiibHTHTKg&(7{<%YrU5{U)v2Ece=~*@_bEJRQ7P}VbgnRUXvg_7T zc^TRjXg>_t`qGahrEXP9WbMT!)H&hSe;mnlv(kilyx7G%C)`Fs619Kr$^|E07@cdt1y9e7zyp3vjf!#d3nB5*%Lkm3Wp~XGAS;2Dp z?2uB%v9=1azL1-=V5WH@g)_|+IRd$)V>SW*W`ZmZf`tN+f!g{NJTE9$XtOc-xOVpA z+?7`DVOpI-o3F{ot>Hh}0Hi^GCjz;&oC6pU)30LT{4cq47#_ zb1Z}e4pHP4p(eQ{<^);RG`|AHu{d&t)ZxS6;}!F;HOQ?=CB##lO_=XrBlk5tY0SbR zTST{VRW?1wR1|0abAgzPtDg+$Qm4MHrv$|!IxmP6kp&?tmr##?xrDK}SAw>ToY5Fp zBxzX-MMGK%dzL4T#i+7}DsEebTZwl2+}^9)-;MQ8gz+JEqbPAF`!Es^^PJ3OtDC*l z;wqI43khj+ydZlS7U_bZ5hY^(V6=qs^;VWB^E_tH&@b2Kp$_E@t_fMj+J@VLvp}my zntI~KkK8QhgLsorD%5*R$zz|Ei)A%L^8G%PkKGU)~Hh! z6&!tjBiE@ED9q0P_t-bI)am7hOm!tgZ~3+E(F%kFEnytU9k zVX9E0FdCQuZr38Y=3)s8CD)d@H$S`wrk2^7w&fE3w`NN`^Z(GV?+#Bv;HE?{9G&|; z|J*{13zj8jY@vSX&#vBn{Q2$2>j6{C+riBjU`nwqX*kmu!@}#t!M$Zkh6Q$O4-peE zN07Iu6pv#RFy8{+r3sI?NysL&CPtb-fD5}8NuiC(A|urvj5NtD7<<2ft;{So;D-KI z8k5BsVkriO*jS=*7GX_{D{TN1Byr|uY&j=jT-*Q29P)1}6N1=aDRE{EFc-srRwxQK znJBabMm9Mdlc;p2ylrw)y-qPQngG#eO*mDSpa!NrRm)qD0*~Rn8E9b1T51C<*|4pK zIU0kCFp)i)6%w2;S4MHMv=aLQ8zPTnvtf>=RLNT&qm>cJjW@WQn^!C|E2$pkxUV!K ztc*#|CX>x(bG#JUu~f^6mJ73C3MUDlN0T)Z8A~QBvrcA0#yENH1$A__6^7j%)0Yr9 znW9)KnWYw}6bToYW^+9`IeGDq7k~fTix-nQ(-`AIPwwXNELySj^tlE17sHmb)9163 z=hnlcgzTeqJiId~Vh#4c-mg8fyj1a9X1p~(-^i~h^m*u0%9O_Vyyw1U^MpsR$V5cO z0kc`5_u-{KU{yH}n;I z*^TyT0xwI`^-<>tLb8)q9bX7iTrZ+k(J-6bL_PU;e&gR;v zLP@99kVfMEtc^;@F=+n?F5t8&OsfG|L zod&YQ#IDdY)`YW{$%UvKySuL=L_K6#bYZQESACevnxqUu zdNCgl#x5GWs8`T*2?h0VM)cK9x6j9LIxhE*TBh8qo18~JeWb&Z@Xp%-!a>`^l}l5v z-Us>dC7KUxjl&T!s5|#VzL`j| zd_>T%(DL+{|GXLVopsskGnJGlX`@o9bBPY7?R%V#4g5Xq0`Ih`2Lfd^5sI3er%XGwQ^$YeNEij+JrHuM3V%|>EsNa8g<2yeteM!BoeW?KUFhAzIEvh4AJd2^ z%0@C1h4>4kqk5sl7rryWHFYj9GOm)BW`(LCbqp+yftz|>8;rhX4i&ZYL#CFUA*g!r zJgB^!g=>OSanl=LpdDRs{hDlVID$*Kb`qlS>Hcalo-k@AK5 z5&c0{NGqtNRJbnir8K)G+^Si3%5ZN|8kmDf>Q=fVE6%a*aoxU-<5qcNulhfyz-bg` z`K7CUdOU_Tzr#4sK7zOKUL{Hnu;t6YQ*BW}5bS1z6Qg}=gaUXym#?N|otmvA$A7Y7 z19`T~1b>rGt4U=rqwYBO0hesY$;a8k1IFMm>{BV$FSRPk!qfXnT78H5AH;3h*>&Rl z{n>KGXWkpy!C#F%$B=`iuPfYA^5grWwG*!27j~Y#s_H^SH7jb!hfrS{^8mx`6K=};|k+&w)`}6RSu@!!@UE6wj?mG z2W#%->Hg7MR!5ef>!JR>wNrR)#2t?nMf@R_eBovB?bpYs9}gX_-o4G|*(I8HR2CKG z;^+)6-A&kRByfMNm9a7Zw2%r}v?_{U|Nh9dZRK5byw$jB+1Aye$DXc&hF+l}DGcL6 zmrqdBfpxP3?~heFu3e)r-1(&+LF1ySSZEs*xVZ9u0o;xtRP&axop0bVO(D=s1Ky(A znw9OKENC|2@aX6;pQ!MLO&U86Vfc}v)rM;v=kpA%?+~q+f*~*PHyi$AkRsWZ`zhAZ zV2WX1v3NMK?j+z~ZTzz#7)s5G?<{q8= zPF+qWAf!w?3dm~64ij*H58dCxmwTT0){-Eywz9`lH$*SbApVXDx8i&45Y%C*VY3dz z$O3vW^mV)aqY&j!rM{&U*yVSW&5@3fwPN}wrq~({s#@xP?`I?2U{(GwPaa|S>zsq| z^SnO>Q*bjDqkN^3tnCPN-65_*K=~%$^?(hX5A)E}N5Z-RRijiLl6(L}KSAc75#f%(zYWHplDxVaVJ=cKVL3jQ4k*VoPNzsWt{w?C^6yC-AbY65NeV+#r5lE*Y28q zwA*X~Xg!-RIL77lv>R|NhIyCTPUT`;4&vHYapM`fHMeP`o1dkUAY2g?9_?R){z&mi0fm%w(OLc0`yahnZExE) z5dN-TaV@+_3LIzMN4rj1bPZ6T=!z!WJ|uy`NGFT1L~11E)L8o82fj#_^%l2l=T8zv z9-q7Cx#Q6SfNVFkqoOxj82T=%@ zylxiIFEGRq+1#ZJ(5s(`QBgIV|f-J7=`uHI@>2Bk`gf(IfYrcw!dUn-2@fvOag_C)ehL=ZAnJs*J54f$W;Ad z@wFCyH*byg-r0!>+iM}n&cFhJg+Pf+NgxcU87l1a<4zEb5QZ9}b#hDjn zM9QXz_*o%~C73twA5ViW1ll#vQL zYo>5vZ2yJ{Wz@`{#QWzZurkX+W7qC(!lngvdcL;;%N`sC3lYfFeBQ*+u&+Y}0AKon zuTfo6Ayp-&Ae_U6mNr|r03}=?iBbr<$cS{%G)I#y$w~|xEA@{i5V$!%{|vWOuIwgm zFw~>|sSnF#<>t*VcWop37jdR2D{*n~<%@UlDo~YzIVk&?jahDnj{InRQX)TAj@ony zEbvE1v)MvU;hyshCY)tEGhfq{kW6AYH=o+<+GVf=E0?euY4G|)z&AG7eSv*QBC3_W$H1>I8k=eqn3}!Wm0CUVtiB!IsJ_B|0;$SiNT)54Lm+-CTgv;Ngm^) z^7;s-N45BMVi*;~p(xB7U?#D(#pWozO}59Y?JySZyE#hr?*u-XX%dJN6HO?_`;*o5 z9#!K*#9DVkQeq5>n;~~j&YZckffGpaD_c^-IrLU=TUWKtZtfPxD(j%G|FEh)S!X5c z1Zd5EaTme`_)8z=;Dg`gyHZ#sACe8~X7A{hWX8ANGS-UL&0T%)&H~?uXF47%8wdn?Lt0W>c%9%JBx zKee6GDknwIgJm|Gg*NL#4QUAN@fmlpwL=*WrQa*JXv=AQm_vdqjC2cMr%^$K#Gu`b zJym^iH{SU)YA)`Ei_Vb$l^LnTN-83hTSpnhg^9tm2gXc z4Xaw%j(uG?hxUAG%d3otPuh?5W!fgDr_wr zl?wi#Zw7tWCHN~vR;^#tEi#BXO6Kb;1(m8LHG83`h1L#{shcn{prTSuZKqJVINytR zA0N}|x!~;k{nPW5$SY?is10`LGQOEhS-*RMECgjL3H<96;rZ!L@_FT~b`uzPe$I3X z?zib}w|k}71AUDyAeUV9_+Tb{AUOtY5e>4Oakl>PBp+pkfWW^?DloQ4^nV^Xm9jdi z$F$PSU?&y**TR34NkxP<)e*?Zx(~P}BP+i}UQg)D?lanKPV8ze4}Dm6fis8tni~mO zn};UdDr-sJG)&%AP5h$os@X4k=T0w}TIn^81H#PB!nWaq*J*41^qk5+^_)*mT)}te z!^u{slTD!`8LH^NdyKDIU3l2?)gOv``Yk?i=Q*pRY)*v$hoNay;U?;J_~ zaGWQ(=S@Z)jNm$93L=&wFa^C-k1gc;tbV_h|s1$ciC{FG3@y z3?+Ps?8=EMb&jaW7M1)bA+5QnNK>F9f08UtG!Py_$|!P$z<4BNMl=(A40(z~A*>lb zSiR2|XXlq!=f;$cQYS>i15pq%r6pSx8bf$sIst7mQKFC(fr`*ZJn=kEGE_OCbw!FT*UK}(l}K@U!Sh1TJg-nMNu57Z%uSTb3%({P3r}TSWXN^BG27X~*Me)7VN27S zj4WU_7E=t9{mNiLQ=$|^LISZEaw<_{C1^KMZOU_`6A2P4HjXB)j}*7A&mRCcS`J*DnC&B2>lFSFhIgcCCj{>oClf%!yQWy4y*9k%FQf z0aRpJIdwTUJ$zINz6wg+KMmz-fa)0D3~^I87t1aZE;=pXVzx$p+QQr|LmRt{DaCl) z7PsT&E@G)h>6ic9LtuRAk<`2zNqwelT4GNn}`Fm}TX;Qf0T z^3XDTxM1ol{#`JMp&taTR7^WAsVI!Nn^gb{_`~f>ok;OuJJCIZe3eVab>tsh3Ju6b z5hv|wUzwl6yWiBi;OJm%=cfqKKN!QocpKFq7-~be{!CpoG_9@hD_r`Z;o(?p4cjE33MdW(xa%j3a*ZOiTnUtZ7(cj{Jhbte;16}#Sc#HVK zuHpFQ$qf7E&J;``V~-g2jQ_o!SQ4Td-dg=(;tgG&8UmVofMHopKL`aX;D(o`rwsWj zFKvbmaHU4##_HU{@$vR3aazwBw`&!&!!!10c-9N`xI`NE*CZ{r*!KTGdX_d#kX<{lKle+F?JH`44&9rZtx9uDHX-!b z=9S@VaY-`Vn0M{BYtyQE4_R80d#>oSw+r((vHm*VmxX%Uoas68%f5C<0LK8jKt;dK z%BD-E1M{ep=C7YdJ*0bm7QKmkZrVF?%*>CPAwBeQ-wywM#`M>J*D2XQC4P2BcA$VC zKL*5{k`58>Tv3vnZw#QtJ#vesxjACFo?x0185`;_^F4uU!nlHhD-t2lgvk04($CY< z&b6^!CvH6j)B00FU+%fY_%0)wCjPO#8W3$zmgbiv7AVf zHShcCIlt>us;3dluMK`>XyCqCZW^w%KOYQFfK7ZaI*)7TuGcPwx-Or+e*m>uZExE) z5dN-TaShyB4m<}bh7GXew1%5*0lKzxvJOL27_@Y zdhnJkD028HH1dF=gp0_oyjGdcAQ16oF8@|YYc5I|gFqx3NtY`PgonUrfLtL^9tjx} zO$F~m!jLG0HN|_Y_wIUn_5S9{n6h5#713}{6ogc1Nf)Wc5bmj7fi{sSk;(ue5TW(> z%=0*jQ6(gB5#3EioCuCw-%SNqg5ljYPf|Vdyi^^Mnu<6kJS@IPkBcV^O;nY~{G|V& zd6O=esLYdkf_|y>n9|j^028Zo&DVs{a3Ys!j9j~o<1M@sOBz)4yt7kV3^Pn5D&$%L zfgpns8082gKddOT*Cf>|A&)M#GcRC7DX{n!o_qgz002q4U^IY0aHU~(_4^;!v#U2S zhV#+x_J@zR*B{_c7H)TSl5}~8A3}g6-uoP9>8OD+d zBrURGBy2{^=M-KiM3T6W{Z1c?Lv`VHRc0B?X5)B+g9-jS9dGd7d%W2k0)Yk95EJ#) zaGpXI4TnW}^z+}Y9!#ZXOGOR*zwlKuW)~(!{8h*vv z+Eg0&k&sQ4Eb4-@O|Riyy=wgiA@*Bot~X><>5DwDa^rHR=&g6_>@igsf^m%+%Z6og z%ehC+F};%F-Y`Zx+rghJ&Z;C4r97~@^SR{*hC5D4$ujNOWLUziQ7*L~h zz15`Ph%kj9^a|xYRd_H+s%H7&CQfrudzAGvY{~nLwHMX8csvbJi6MYvuqbDm5(~F>92PgIv#PA!5c`5%3&);KGOiDf83j`w2q`hp zjr)fxSkg7}gE&;$>#1bgMw91o33tZ$oTu@e$ffdoJLD$ma*1*tnqfDvuQh%*A6L(U zY*y~}q5IR|rTLl?#ti+RJXe12#J>g?5FN}uKezlaKp+9HIPQC&`Q}` zxi+9TCWk?{ox^MX=5^iltA2^?aUQCZjkyX(we9j6dv-_F+?Gbk%5BE>qcLefU}7gc zn}aG-MXEF`(CyR^s3&drPAdbczbPEPI9gkD@5NCxkGnhe_s_vwrOmDp2Ko30k-d+WwC6|0;r1O3UKTn{1feAvA>u`M&|fVJ?k+?;xm7 ztT6*a$DYxL)>*jbXm40@XbDYc?WT?cu(*E8r44Hk9-xTa>}YtP+5n~9?8(nO?d>w( zzHV^CTycW!hN21VOlLbG&#vnR_(f#fcFHT~lfEgCM)8u3QRew!8qQ#uTkt5p94Eo?bc>e-zk->@^F${+9ev1Fl!)_?a18ith z!Y-tgQb?hfTs*cWBkHk?E$F(%rXO0w?lMW?;YNg_o(?IjiobUMUT^{H9XnS1zYv6&80DUYqW zOj3i#K;StAnklxy!v!wbQm9t#BkQ-M)Xv_v?GrE|brQW>f<(g^2 zHV2yiBg+-LRgqwwIffZ`j} zL&DcA7fDfRu4q(9%I7V--=Z9C47C+JM^wLLD>oKY7+TH+Lb*UR!J6e>DMoRfkpxx+ zO%&19Rl+D2d69_N-%QFjR_jXlsYsS-KRMvo4qj`~xYYN%r_u!$TvZrr16}%v6nm-( zD-+~c6gh=kM$%3lTv0qNJOx5gr#|?OmCuUh7$s}4l?23LhX}kGTa4vHRuHQXmHCaI zO0P8O?R^(Zn3r7l9h&ceGmb+GD@|ILj_Th69yGDThwCiT94De0MdUmL!~G5jT@yQLULA++cBrHaH$H z$4#3lHPl$*Y7+BpPN-PLkqR6whngZXSXw$J}cdOXB zLC}&_j~DkCQObn0B_J?4Uu#gxy%IRDlg#h9gGqFc6MDeANF3)8u( zOR7(}h2r|YOiz}}U$=~_p~&kNx_GsLx3Y{?p`t1mqzrQ!i89$c>)1`*!DER`>CJ<} z3{tZ3%#}D$&t7Retr4$lxLVtu1Z|qs>F{N3ik4AJDg-h`ltwDx%U#0Gvs>CF;a8** z3l7Uda&?Qc0&}kvk74Hde!u3dn~XIxDz?i$=Zn0+(qLW?lnq^1WjiFA@ep>OZTEot zt{Ka^2KOIf8{OiX7HS=${%(fdM@~_CyT%wbG}w7Oo|QW`>|;Fa>DlApH3nL2u5s>Z zz`I>&38jjf6pVPurp4-Q!f*(epa1ds>h$c()jyB^12ecx6j#rgkJG4XN>5bn+^*Z5 zDz+E;_MuNX@|{iq8T2v>9#$Dc;z50TkcK-LD(nZmLPgddBRge+v_jk_#_1Y_HLw2u zqTXxC;&9zF7A#o~LznuTV*pkuDJ4=d5Z}WoF0*H#N&}Sxhea)jGJNb{z<1-IsPQ>; z)C3Md84TUSknkrovZd4jBiFHQsANpD;Au?LYG#cSv)%dpNDx=@*RziUbcUHAw(V-9 z?L%+oyh+nL$W0=JSJemnXMr+4WZjbxpVnS>*|Zhwd7F(rcLcSpQsd{6*ToPP@rBNG zA3{_ijK6I&mf|>iXl!J!mEmowM(FIqGgMV#7S+0eanQcrH}F#otquYV&sKw1$~I13 z-1vZ=5N| zY%f0+w{X0?TvA$FHN(UW5x;oYQudg*tvNJ-w6510Gd&GAJDuZEs9AT8a^`gLa`~wn zEt}T(GFW*sksi~^JPs?nQ+FLzPeHZLWYWO2P5-DH{0$asmO_H0*{?Aa9H$@{Bx{`f zAvw!2wct3H=d@Xo%CBo&6}EMCNDKHIP6{DiAqmVKb1W~mYiEtseG*3LxEEE2-5bEu zF6s?g@v+FaXIQjxubCEy1?xb}tEGP_=bOsfYx1gqKh5Y?G4D3}qocPhT}~9YDsiSu z-V>FuD`tP}_?2*&v8#yq$a2r>Rp0EYW^TmP8P@Q{7p984H(dO1Bwvd2GB|z!;xQP$ zYwCG1{B9r6*zQ*+hZ+NUf$+?>Sof1AKSBl0DayJLy&DgKP%B@BdS?HW{rvXX-91C2 zacc(+N%$;Jnv(Eyc+yDI@T7S?+>_1OkTCiZp6nZhjGRMU84|I#m-Bndww&~zj9zHM zSXMb5WH5&n$~bN_;||WS{cjU4`Y2hvdE=F39snVRllrHZXrYVTGHTwbm2<0N-G1lq z@7GZ+p{kg2=g5bf4wOQP^z`qN+}FZmXQClUC7%dhbNLhPwvt#VPHk)M4r6&i5-lQ_ zLDaFFi(If`yTy4NIK_DCw##zc;OZb^_f1|M0nA%)GaOd-izcsm7r>qc9ap7^y@i8? z?JXWA6Urvi6`|vvwk@2vt#~b7bD8?`XK9!05bIqXE_OK>2=L8aiVI*9zkh9Niq&IZ#tMAPXsYMRpH{A%Ex ze^dNLG&O$);zF5C%CPK~z;%Sr(C-FhqfeYbMpotesoK)?gYToi0PR@8ZsRr(z3VIH z5TJIDP41g?lQx?G2HI}0N!v?+#h4mdY^b5Yq3jq<|GgkZ*^XD1v%v%zTu3|TSlQ}Bdj8tF7_MlH0cGo@)3ee8at=l-Vj<96SXZIfmXDw|ghDXa zw21S5nOt7|c6${2$anu;*%_6tVn89tBme1 zZB<()_V<^T#TvSwaNYRRENf)wsjEjtbwGJP;W-+e|Kw&(MeJ8MPG211bHiR&v@tU~ zIgErwEED$RiU3+mq(QF-s@hOkm2#La0gVA2rhqaiLedrL0vlgi_f5sRsK}Q#hrle) z;L8_pZ{yd|7(SD491CE&s|!szII0~9v5)2Jw1aLI#3VvFcpHb5tt#X^po5_+rr!}O z@ayllcQ7R&QCL$BD6k-7!e@vYpZ*jXggKgq29`a^)+9bBIr}>cOUD9)>u{B9RIYa7 zLrM`66wX$0+~Fb^1}pO91V(Iq_$`!=k5inJ_*pkoqZJels|a*P{yN0$vK)=y!+A^U zX|@xIYY23)Z)H%HJ!%GQ`RI^z;iBDBm4mcwAh$_`Y4*-aP0AS9c(ur4@} zmQdoHU`iC}*ExF1k744WYQ(QXD|8h6zwFS!M@s?$Hj^R|blNPz#Z*(-oC`1&U;F^@nV zbPc|=LQq&^>>_v*8Hn>>xI_tz-n|=x=ozCKlwXN*(?xHsGwMtJs0F(T@<%Ap2#w#z zTLW6{C=2i~a@2zCHOc?d{1$2UUA=N0$@n`6Hmx1%{iNAH6~>}(p*(Ro(hqVfFc6W} z?tZrQ_qL->7r?e7EwmiA;e5y)y@|fHV^)9?>PkOSzHc*snVVkDWU|?S0l&81&zker zc&c$OoKx|L1I^-*6?H8V!A#GISI3#ePAn1fmHrY7bF1_1TfTI5A+X)Y401KqoR1=| zk5p*ZaWJHvzW3zyN3yQ0ZT}rm!k@vM!Q{lYnU$M#xJ1!lCf$xQ>Vdp!l#J;mydpPS z_pDov*;#`ql(T&z`mj%{tIjGk9dX|9Hoj_mT0e)41h+DR&|&X_y=kEBs_g2b+v=>s zx2xrrgn#frk6QdovrJZmg8;FoRJRlju$|>}IhfOc(B3E|*^$yw%Wv9b2DI39kI2s( zY_QY6!eJI6NS8QR2elQwc_!`bCHlGE*CISON4FvWr`Bk9@0S|GZoG6nvqie`Hl|3y zUtx)7XVYFoJj?E_?C{EDq&JyYTOqwY_8MWY`1@GhCH;xCe^Io(YemS?zSg7idQRtq zYiXIRA06#nBi3)-Ag{w~zkEUcb`rk1c-xh1ARnJ*|78CHy%}pyBe(W@e#O~Ux|u-% zw5!|dDiGLQpe+&?TA(XkEvv|xIg?oHv8T2tY*hH~mF;%kt}FQ|fuG6bq1AyR{gKrayPR&gZw3K9~0C38IJfra-1AATk?iX46u8hJob z!f|9zJ}QTE5Qz9Bga27bOD;+ogFqxJN#_d_`EcGwFTQ3|YfZ!D?aUp)W-`>^@jgL45~Xdr=M3#b8wDDzJDJa~98KY;!H z6*A@@xY(e|LWCHRlxdjZf-I?!CQKZbI>=r)w4X^jV>Ez3aHZkXGjR^1&rbS zQLo9<1eS!Q2yv*^p}QKt?r>_6MtxxV;iazFXcmGz9DGKLaKmqa6&)W+w~H_7X$<>rYQKttgl6Cz36BG32y6}rd|j9K$DHH3h06K3X}A&{I=5tT3yBBL=iRb{q#1Y(>oaqX+bEiG$T!gQ&mTA~p+puZP{Az{g?onec zKg)t5&t{goNwEr9G#Zu3&~LC<_b#tdE{Cl}&>u0X^l>I#d2zDloR-BbgV;1`SaSJ4 zC@5zf6^!YH6pyB&wAVtojH(7P8Jap!u*3~vqUnrbTONo~9$4Jz)LKoZf%d8~KkSNg z7-tTATEuv9)Dn6yhQVOC-3e z1&}E;lpO5I7`z&E(P11P!+YQOrUSJ9y*PAKA}QCAH@KC}#$H9(6c!_>*ZWD))VhAtL(xIg^yHqcXl;T9ND)zMWxEZ-r}Dr zl^A+1UDxhh-X+Z2?jGwpk&f{9D5a{9e3|evrK}0rrI@zoY`OGWZ{Af4!)r98$ycME zJlYBSM--LsJYJ=!(&D=+s$8)&6;<4Px1!7tZIsnq&+MeBHXY^F;iWptD%!5PDHlLZ z-4r(+x>Fzz8z=I(^&wp#T`64yH5v+h@CED&` zb8;9C?WWW%81P%dXh1bv0p&^~u|4Q?`rnKDUvJK*(~Z4EGr_mm#GKa6VVaF{D$DEj zYdSsBSew|TZ>o$UDtqi2D~5xv5w+RtwGE5q)UknisBU@8jNce%imBsGW$w&rb@N%{G1^Hnhiz=g-eyT&%T@Yn#NLgrUl3DYGp! z-F(0LfBwr)kV=WXx(Rfv!L?$h5Fz{JZ#2rF(epc1Sb#M)n$Wjz454+iS!2{6l^MQN zO@CPos~u`7(n3B;+68fdB7$P8XQshQ9G7(6H_vhE?>@{HYl8d*qvl->W(xZyO!SvsHURf0K(zQ1`y%plfwZlzKSeSOM=%ut#n*Zt~jfx%a0 za<%ihB#X$?Km0JUyxhUvmqpGcI-I=pQ*7DJP&y+TLvgyU782*HTMv`+ zY^Cnqcy3zn4SB~pHNC<9a>bbcc0ay$pRLqy^#`R@U2oes5Pa9K*a|sNvK=|O0)4Qf zIzi4Y0u;HsCczzGIsq-OETS?gkd)o@lK*}nE!px%f}qul#MR7dW|v%@4T`Mryl%^b z7TjiBL&|dmuAyMcfK0(;2se-OREkF^Sn`d{F!I3RgCvAcY{F5%U(%pRI4U?xUF2M^ zI8h?!A1nMPsa(3W%yUR&@u>JLGr+_Ya-N{j2wbF6&6(j+bfCzQX@mvGrQ?0Mx*T5L z3~ef>(qzoQl4+Q7Z4{rB2B)y(CIe%gXj!TRA(2z3xa)a>%~2ODSv#Vaa$ZP*!q7`8 zw9JuSiJ~-p&nxw7sNwen3#*b|i3Q8~^g_+bISR9W(YIA~wr`B$V&?LK!a|`&VKguS z+{Fqd7kr9SU>X=`hptO}r`Zg9)xus!D^3gom(9!pNu`A2;|Wu-pGrAl$|qsJVxF_o zWKzAkTU{^7nbup<@w-7W)o`I^3m5VJ^Q?!sUEinVKC@X{S*w&-cd?v=x-h=c{&P7|D{}a z*MHyrc{}`LH>u^?#4j`Vd4rxGjP89$ZCOQ>(r6tBTcQ6%U3RhysUu-4q{M!6uU-XH zY+kjzZtX?AUhfsVu>0Sk(V)_hDmizt%P&`l3Y(T9vAYl+9-KQ>CMMYWPUzsP7CItR zC`_-{B#g#kCE9I0d#a8inaJx2&FPOnian{PWE7Jp}6noq$ z(2+xHh+#DDZ=yMhnaLmqs*TD~)KzFel2U1@Acm9vPBwdv%RJwNmzf=GK}|WU^M?%~ z_Tni7gi-==99ObO6=}SSZG0|TgWK&I4ub&@(%+-35&N$aX*Xqw+i#w|J&A?F*~6Tf zBnwEF>UQ^nbSecMIC3vKaMGzu6dsap^VlN6J8RYXI0S37Xf%czxv%||;%|&>tpIb4 zdv1c!ee6?G4XSt$1|-s$r@h{RK@yR6_%q%cZF7Xt={TyMeji76*A|J+na)t#J^Y}s z$XSBHi1?I{4iHNG)or!(eKPKVebnVd8x>T^-j6$Q+JWQKFbw-|asR5y4oNw{>~`V6h}wg>5$JyJFHONf7m5p$2ITJGBoxRL_)!$WZyKT~ z@}ow>Yqu+l9XLJN@A>vYu7^>(G2`lXG?)$68;h zHx&KPQ`|;GK$Z_}^Q$DJ$+BdNnk5mTyIKVl8FOdGjj>0z2ZEA3P@khOR@r9AKnCar zDH54E_v_qykIm`taTFH{jiUk_!60H90>%-ThL|V=G5`}H+%Bep6bpz6eIOy$3*hi& zT7gS4W+>oB8WfbFg44h~IoIB%PUZA$iNBP}xeF4`fy#KHSQr@~Vgj5|6dHkvK&mM* zOo}SR9EnDlF`PTQt8Tl~yX{z4E=v;;19PHb!n9FroEV(IoS6uWtwhU2Q3NU{F5^a_ zAjlMTOz5hjVOvgPDNvYUTM8{X4zFYwqBlHR;RjyRMiK&L$Ts zOr|JI=aFJ;^Iu0@$?yti$o-v%b}3`3?Toqu<2r>Qva=X7NY6cYP@wh!;oib&D>}3x_d?? zm`iD+;do+!J(wrV%@d7~J^<6AWHN-+YQRiMJGdrO1Sys`yENH3;~XXun5|3@&MnJ} zIa$=#`eQBm!T}8gQUL}5qYMRIK!gf`_#n$k22Z?UBDB|Cg`5~RL%slfj8SMd!}?nP zvW$I8C%IgKv)0x<-PX;OtGOL2o*CxT!;eLMc6az|QIFv8xTsGX?ui}YUGtJo_zLF8 ziX|ro%dg9uu5G1K-Jf)}v6=R|yQKXbK_5+`gmz;9^9Y4)OiHkZh|Cbih{9hmCqT?{ zb6fdv> zpjsuFI!bTHH-Fr~r6N-8@jBu6FSrD(o^Y;uBMbNLHTN^(A96{Ry%!vg#}U}p%Ii<`cE_zA8=vM?c$8^ zJlzuZ=blr%8R78!3`3^xH6@&=vbTAD_U|&9HoSH9S{2O|vTTxf2TxBniIYAb;dG%ktP&WWF>W*`1F+I2c*6%yNZDpeEh?x|Li zfy$?OmL>PrHygF!j$T;sRV8BcJl}hjxE&_X>2J-o>sfq4NWND5XwvC4d5N8h(W|*% z4ghl7Cl2VFgt;l6o`OoX(t@2Wz&ogrk1FZ-&TNk82f}JP9hd*ruozbK%GPQ80Grp7 z0V{2mo1M6GoU*>TH}@NbQOj=IFc7@+E9Q`c9VGERuevpCAm|fKj|KvoT3JM>U4mT3 zafAGOA=-7&B58RMAaxqj} zvrlc&P7*%SgJ*mYZg6=&+*iWQblqDYepfXyF4&y$qB?l8Yl81%pq^MWmbaQbuk9+3 zc`^V{2My;K(`b|Ju@VV5(k{v7D-X^}BH(7XQ@4{wXEQ8V`1B_w0X=%Hm7Smx@ifgu zcr)Sit23Qd*Bvk!RpWh(!MnQK^YctP`x7)8@1-X78_OzqhqkaAo^f~QgtMmNbhHD$ zfD+k8IBbz?DLC>Ri=Mm*d$pJ=Xe#tS(%;Fh(Atpqv@v$8cs6fDP}!|*G(O*Zt-zzFK$YMsgvqKd z6|F!s{>0{Zl0d|#JcBWt2_*Qg91<63;Jl1)F6_GJgsXbE!H<=RbfuRPxY8>l=9L3R z1xUe>EP|j?nVLDFs0&&mvj_`;%h-2wosYgvMu94-bQN>3WEKixoe?wduz;m-6}aHU zDsMOfS4C8Onk2+(w3_jqqiL>ctq93Yb46B3oPI@Xk>+UnN#gCFke6CZ!K2px9%!!W zno+U6?}Lw?IqJYWz4Nr`88acV*j4sXT$A>0I&ntOJg)aO+J z-39ZmQU<=@LJ(XjD&YJntY?afi+Rn2+-~)0QVL=cxMbEs;~*r<=J=UWA<-m15&!`0 zXHsw|J@H@*9v`_P>x}0v)kZ9sLomn+MREnL#G%8Qhb&U9315J2Tr zoH<_Yw%xWS@z<3$mouxD>d21I!sV(!82omU@szIJb+?jRCSD*8gLHad8MS0HiO|99 z%?uZ6(dN6yHtk^d4Puk@&R7>Vg|oBq<@L?O-Dsz%H!RWdD$TOCw#K$7p_NaRb4x*i zHwcG^4e{h5&qtF9d>9OxWd6mD)kcsj)1yrt4nEog2#nibQTYADwOD) z*KdCO4`gYoX_(e9(wBXjy&mSnYB`>+Ow@HJr?V#X1ehKwi! zG6s_%+|1KhiaBJ2-jNx$YT)8M>ceLeG8FK)G$<%T1*fsIeALC57M1CwjQ=c^gNt*{ zfy!*I*eo$XL1O_rNct(NQ8 z!py^>i$S_lSs8USBn9^=iClBlX;}(aQfXnexy=F zQc*OmdueduN<)#S@JVPkm0YST8DZ_I1^{4J)?SA&n35VYMkQo;Z)Q0OOFhO}9 zF)@Q7&_!V3bqh3vuvjf;Ye7L1=(#ZD(e}&1`OWlO{VA3TFO#UB^>TiX(k2eJjq%uM!{+GdE*;vb+ zCdEA}V>=eEFU}yrM-m}*{aUn-k*0=T5CEQ)PL`8HG3()XECb0{yd+ z=@)$bDi^zcDkJo)5Q-Vp6MS?={{#w?!zwhvUW))2+kk%+W}Hy;8gCj+Xf&D?gt9<; zu^7BAjK^cR)kbj?-ZIRups&xc`2FqZpVp3t&d!Hm^VCKD>qC_;?9f zpRI-rHzPvJZxK+s7Cwb0R{~OEXaZi{o{H;SXyoz9iBm`zPoV4{A6)SJNu}AYFV4KW zYC?V1gnH;VcfbrSOv7qdqvh=nw-YaLc`eq%3c{~Z@v-Z$L|86EK~A|EnPT@=x3}#d zn*$TZp}703yodgY{js)ZexA;<>dr!;hDE zRoYjwtO)g`&>2%0L9Ap7i7}ZzX}4$0Bzb6G#@ggN;im02S4vFM4!I25*z5Q3C?cKk zsMGr}=nuMm8h;oB$45u~ZqOUXbT9~qIH-)%inggtQz@r7LY&|IIrJrn}va+Fq+3k2&7`$~yrcaORx6$nR5r{sJ5uv~rGuHvdh zDTNZ8R6uTnc)M?ea-6IbkeDQJ<26BZo`}a;=2S(YR{EM~x8$m3M^1K2c7aRUJvCA{ zGKrle=!I&eV2_Wa+{4-{_vv@K zg-6Ttj1QKV6RaJ|nfKC*f^CXRT0agMc4LKQg z@^O2h*_T$sG)~7_VMuh*AH3-$5D$TCLuT7QBF2A`HYLMGyuu zrU_IGlpstu%UH<`WQ_k{3p{Covk!R(-m$qr3D=5Ha)BCN$L`I!@oP@FTD~dl-zn|f z#d(?nSJ_63MMA)21gYRC4T6xd(o04{$u49mGJ~)ZxOVH_kB6g=(~))MP*TDO*33X8 z3~4dX2_skwN`NdAqjJp=xQZO&UaKY95>3YVmeI{nEi)xi(#=pwqf)%N$|=j~wAISZ zF|&6LCZR4CY=4g>p^h2HQ!A_EU99njR$EIZ!kFbL0Z*AR5GxHVO=0+c49eM^z?dL6 zXka?Ky!teAGEHZfqsgSy-^I(X7X}z`C08f~N`kd!S%w;zR=JGe`;;`YrAP4H?KW4G z9CmG=wg&(!z1v5~m1ed) z|4NV&)IbW#m#{=**a8iN&V#FkoYc!jj>ZVNXl{0>;yi$MTz)7FVfR8fMjVTsNZAV8 zmSV$ixoyvfD4(Z-L!3(&6u7;0`skcfXvMEcJOG1f-0yFty#07J`SA9A7tX9sRXGM= zfpp>Q5T_~~Z|q_D%DU!@SA}*vF z33`~Yx z#8&Ha!AWhif}F3PeT|{Qex$Y150S4DwvR^2wLIOv`?>qvW}I@CURDBF>?X|irR$Er z3v9P^xxihzgq_1d+tF+Ut%QcOLw=9n^XGL64WgZ@N)@dt4Qi;75!&;~axYE;<6GOw z(Gn|+!cMRDB@4tKIuB=+z2_S>q^pUX&87S(h(9F_f|oEKD{XdmQQn^@vu}d~a4ZQ=qxv*U<0Octndk!f5BG)~OCNIuDW&P$cy}v|i+dhRNRS@^)9JC*s`ZU|3w>pGn;-DBHouln; zJl9rUK)DiHNjrss$+JvpGO*Tygj_&T)MdvZReJM7Z=>~SMaXT#+ww}7nYx*D>OEU| zZ2|7rG-jzm-*C(JFv+D9a&a~NUY%{{9De61a)H>I0fyc33uyP4eP_Rbk};0(6E z!oUO_=tB^6YYrAjC|6-{HJyFA9tEBH*PH7%#<-=ZvQAykZD}mRExD*I>0suNhw$|x$%D|L}<7c87Xa=!rnv}3yDz} zc>gY-H^p9k2+t2+W#O!Ugvt{}pf)`|7=UW+KXm{`lgV!lz-Thr2EgvMF95qg10ETG zpRNA^eNsV=6EP6H`xSlQu!&G|XCsIdva6L4AcbQt9=j*g8jss*w>JdDt9Sw*L*q$y z*&y)6Qn{`7Rus4e?GSdSq2+jjWL<9$xG+%XZ`=&- zY(p5~Gb=_%D6ncPvd$A{;dpp^p1)(Ma%~9#9H*1ep;NHYLm-DSBT#Hv2CKyJHKc$x zBRmowEAPX@X8Uos9i^&E?X1GlX7H3%s40pbj-(yvm?U!%hv3*($4g_P9WW>Bo)Yah zJS2`7b-&@5IpF?N(Ny$y?4+7L;}iT46{Q^oC|67L3=ApYfN}KDoL%&wu2>uZq!UxWdhM0{~FF7|$H;QK#tF3M~tu2+J6>&MxbnN)yOITjCVyfVv&W zCjQOr&9&04x!$=J)FKfcnf!mX^Goe(i?6D`*?Ga=e&L!<8F%!&`|7-0m7~$@`3RoF=M^`w-~xdgD4DjP7GPHh*PCLY{)evY5k~+Z9-{#HdPV2${;A z#;`N*X2b=Blz~Wlu(<%IrJ#&Z%ip2ZH+h2We3+ScWGL zaCncRL>>M-boevsf=eb&$XD(%U$e@tl!i|nwMNJ*_$1I6Zs1j!mjcb}Wx=?J_g|m? zE1PSM0tg8|NaMFE&Y#E6@T2csb`~1Fk>L7bB!5!$XgD0Z&Bt3uB}E^_#a3A z9svN9n%^;tz_ex?7-vdalon3m>$j8E0WK|!1(#^x6pl`Yh>aI&+7IK!z}VJT)MH$* z`}gQ#%`tZe+%?kYIlL~JW(8;r5!3#RjkBwBc<3g+e%oppj?cU@)m-ol7L{}l;r2FD z(pX((HVN$0VCJ)$L+W+ELp@i9+f52{r38#~%#>yp^AvW49*h_9w~150d&8;1G)jAX zJg7obSW`}e-AT>K>3dsAv*HWlQL8}wo!-w)5?0XS_K**g`z2&W)%UAs34{C@SE z_ZAl}Y#0>mo)=XCvMT1N3A=niEgb9+Ab6qEKpHuGS)qhNX$mV;g2qDKp@%%KjSc**f=m^q*hH*CQXN(&Q8G|`#$M)nH?i9~E{+`WXP4;CgCYM|;8=_Lk1TqcEr?FZWA;=s_C!Iwwuwxcxn#Ml*$S^+j`wNjSGe`wwpTTwqWrnWV5_!eAp8 zdbAz$+7$h0zaWnph}uWrJrv>rv>GV;v6Cbel*D@q9~K}hAzbiF=WLXgYiz_xQxx?I zjuP{(P>r_IavPPqg4&TM=dBt*Ds-`nFCf99w3~S4p`J|O=TCSNe&gnC1g>Nx6b3syYP*_2b<#f4;@qf_9YvcQ;P5oC zemFiKl`q|-4B688Ifd~yOZNTljTu5~T*Z~W3uu`BgzRCQ(Xi`y9LQSDu z{^S-ln+t?-VBL+Yc=Ij{`-RIf6EdmETQ+vZFVWWde+*uQj=W$AFG`_uOb!mxj`JUs z&;^2yT$YuMO_Ibl#U-4A)fG;T3HrKTU-R6qwka7q4`==tde(}Cn80}NZNB;R{?qLr zSC?s!+&lY*T8M2o>mFpPa;bge{;Bci5MFK%EtE!+>o?l2Ihz|v?MujkL+wt4a)au)8soz?=n6FyhFY5Tpc@ZKq~7+ zvC!A_srX{+2W~*)zbdY&t75_^@*r>)t_Ta#$+a6cZhZ2L!3Lny}o|xx8GGq-6J_2l<&z=uC&ubqnlW!lk)&pk)-G<74PE(c207+c zX15vQgGvqDWIwbzMVET-31rAcqy2l!O)1#s66La~~dCk{Pi2O6LN3THL?4}BmLaW^fpvD%64EP2HLQ`S3qJ7(oPw$5#_9&qQ3{7Q#%h5w*)r(N1?^sD=kW5V0jM_{)T-2% zd(uvbJtNa>1{c3yjE1XEllkKc5qyxaUh!-N88ettQ_@ z7T}ycI13Qy`Z0?5#w`&!c)d~rcW;Lht9I#BCL zhcvHyb@%QiOcW{2AEl-p4!0(jX0U3BRKVNKM0h%FGOz1=>I=Q+wUeR;WSs;)Z#F~` zfsY#%#m$BL7IV#TduY4y#sZhzawfo7$3CmHQJUrmX2TLKr=anx_E3hV{*_yScBOJC z*bg+3Fw<-;peol)=LRYYYC^UZxAE0NcUw!$P|_i%`ql^BdTvOalUCJoHHE8^NN91!p@!^qjY+3Gwz=-2MSb0CkY+ETzULqD`g|Fl-|ZrAco;Ld3s}?rf?AXyB#bE->Uv=^gq2-O>f&U488kT@Q_1NY|TCC*Vd*$ zfUaqqw7`G>g)GytFj*2rDn_>TzYmu4g)Ysyu9$8n$jA2{N%-j06)s7-2MHX&q)-I8 z(g=#c3D012-~wU1D06ET;Dr1pW_XxD%x}vMT#8hofg8(cq(Tozd5oMCTbB!Tv& zT}GOWetI3WZi?2i{;P|h6%Gxf&xOQ(J(FnK1|BFl=aC2rgw|kVbTy3nwVDtdgTZR6 zvfb$~v(7}|!unT_ZCx<2sS*TZSp?1B5snIG*XwrKrplVb3{RrPQVXJO&Z=}8OEgT- zMAj0MA4$#Gc{LY|u){`Zoo$x4PyHx zzqeX-B1%Ip_UuAt!B{SonZ@W+va``_yHTeHFZKEzpDjpRI=xtJZ+!?WW)(sih+(N$ z?8mL&GP6}*!N-%eFgQJZz8G74``gmq=<@|i>jh)hPOa6@%2wZR_EgaZv(tkb_8HWY zUM@7@duVt-pITD9Xe-0e(l|%`FDtRZ>-6fW?xs(u^Iq>^aQAsM9!|bK-rf%flm32Z zZi7PJ?+W{Xe?V#Y;orKu6KL^nU>BH%+5eb$Ql}IFJNFN~X zfpv8{!SQlbqx;Ft{p9g_bob@c)vNd8%?$VPYi`Y8rIkTAZj5;^+E2+ZtygVN<2Vrh zo?kI1q1yx%_})^=TR98A$#&lAHf>=ypIXn$GtbO; zY#$G@B=fwZeGm3wlu!vVO%bSsj0goh29+Q@E~YVO3&=?LolNl11EcSU0bG$WMF#g= zAqy!AIF8NAskAnSA)lTU`d3^ScX6Jk5b|sx=p<1g zjN;tb-CSP`Zy$%+m5EX%M8TX$h^SP8j&p?(%&AI1X(p2AB18yzWFkKDJVvG{GZI#e zUM~1F;|!U4x!_Fl6kqOgmF24EdAZ!Ac4fj;C<=q>fA6D)*G!>^Nr*jN50kKSO+QSD zln`?P+!)velw}o02?_)v5Mnw*NO6X#)Rmi|7;{P0qFZRfJ}`}zc7bu=na(m~+F1m? zl@u63p>ADZ7Y=6NPr1NV`J$;b13#gYBwYahtB^$)IR+7q^NG=%flt!ZE;vd*a*C=8 z2S0RF4Vf07l3XQR?A&)p9vMp|NLb_zY}SEJvlOSuRJl^6-}}!40LTQL5v4m)!CsZh z-jb}Xg0L6Y?O-fSLG`x}rx_7ss{Ot(y%E-aHKML>T}oSC@HxYBbAA~ zGpUHCM48JfaNx(Bw?G^C2tzK2-cQd7sssfn4&@?DQA#pF>G*6*hvix4q^v=`KuzEw zf^Sq62fc|Rj7gp<*KbMX$krksr!<5(XQAom*H@DUk%!73EEf;@ATf=*-700Tw|5VJ zpWSq9IvT9|5R+752L?OT82X^YwsG0+F-g=@=-{LY--u-}XbGn0I%wiFKnDY0ICqod z2krI;Clb|V$=AFTL$W#}?w&^XPorWcx3sYF?KeCr zkz=Rw_3Jyz0&ED~P8f#slrpSOt@kscG5CK_`C5Y1tb(ASX2_Qk=)>3Q&$s&ADG1v{ zEi!Bg0p6BZAI_fLrTd$+>)YP4H$5(_*FS5HgYG(nUL%mQqi7=>cT2yu9LZ}03Tq0*sb0bmI~N#c2f4}^4xT5X&Z5E>0&OBgbCE007O>BcOA(&RYqBt z#ue%>)NdK*QtFheyJFOAH|Vc9!0gW|#Kz^P`HcmV@2jGXAw8TuuaD;=&P*R~rKk6K z_NvH4`rDcAPPI`}sApHipO>bWzE6Jo)TxF^&}<4=JI0Iy38Z`v=zF_YBeUYhTJE3zVr_ID!e zV3Y}qR7irbD4223DO^UlpXI4mGZc(}VPo2h5b#~z!zGIaDcovDiVHG$pN5rFTgB{g zo&Q?nFSQBj(o#xrUCfLaXAVpykb;x41ff!Ga^{3qyC@_vOPC0nhPbQY`QZ9~;8O*a zE@KW;W|0W%jEGA|38unjaJ~|&OT!6VCqeOk6e*UIEf}9y^mMNCLMu}4>0B$TB|Y7g zt|;9}6qU9Ob{S+kGIhZGeW)CnlyN%owuZVZ#QcMI*Owy}3wa?aC*}3WGcYOZ7;wp~ zCEGZD%1W1Mv$gB{(Sl?}Mf6t$07V%~!NIk%4xa~iA8zi4qd)%q^e`NZ(8s~iNgM3? z=I-~iEA;WxVJoisH9#Ln?_L5fqYn;$X@y@5K0JIxABR65okY)pHxHxRhtWdi!J1?> z=I-p#=h+Ony*e9SqmOqrQRuCIcJOF~BMXL{tYu?@)1kpSIKEfI8uV$w49gK~Ezwal zl|m67GjbUjAIXvg4E2YVAfX)4(S@H6i!;Q+<;;HZbJ{F+wW4NjE9uZ?VOdoaQ_l8w zo8)Vsw|<*?ut>;I67pG=6PSf1nI0d1@9wvHb#-&BR&U6aMq#2wQm^eVd!Ckx`$91F zrgr^YtMNDC$go%vI<>`icg4UyQYJ0!28-qn_3^g%+J1%eh$V?#bOFo-0I6g{H@{>3 zFZ=SpXp2;BjoG3D_vETGa6%^5R=8PXP3z38ma>D6qzTC%Eyj}j+z99f*&*E;!5k26_j^KI8|MG#Q_04gcFQ&! z_oB(vtq#I@=C<9to_=#t4aLU7UZZ$^Me1(4GNF?;Yip`#n$?V8eO%8&Ai@dCslBf8 zvXb60Hs9a=j{X6iR&8_JHWL1>U$L3fl%z)%ZP!UtxvK3`mBcf)<&oT`nW$Aka7p4! z0UQ97%sb`3-vE5EL|biIejy3$KKu6U0zaH*N!IE32OT(o`-CZoSc`J`50PEDP^C_visg@${5v5nT$HCNP>~gq z#fb*OLr57#t`HcHgj^EM1n)tXB2fq{hHI;LeKi{2&c+7HdZ`nlVNDc-Olir2Tw@4p zrW4R65+!m;5vT~Q$9|{7$r4pYXcN(VB$k=r$n|_AxDqMOC%MjYeb(vZ>d(Z+j}$ZG z=KPAUNXo)X8Rtvny4g7U3xszzP>r%O+5BH|jWQw>&rD%#=4M(VS(b9JmQ16v84=}* z7C<0oE99W8WL=@36^U_B{g|g)X$*nmdI#2xM26gTvLqY$zS5z{wbKJb3h zk8PblfD#TSOu~!GbPo!V+jWvEQEu9D{XIAQjOfJI!Y?KJsfdj?#OXmqQiV@WU65QS zLVh(9{SHluQoA|DM~ysGQ2uuQ*D+rD2M2JmA`&E4TsrXWMkd*cXoPMV@>!=Aw{PQ9 zMkHB2>Am<=+UE1JN?xPU0%Bahj8OuuPzFLVUBG>U@bs7B3R7?e$GVe$8BhN`nO)s~ z0xG!Dglh$iS7A5A^IGNuA<_*uUHhW>5UG&FG~dSrb%N3mvR;HGD#Jnru0@E11d?lU z80V-I<8j;AzHTTT=tlKMB?wOo%tl19vZPD)g&2`M)ot9`Y-3#~Qmhs1YiAQ1hloj~ zfmEAjPI94vfJBm3HLL(DrsP4&C`37@7G$xod6zlWZnth2Q{Al^4nbiW4F*lE7Pph> z&E<6u&P^e<^F0I;X%Ei#C2c!jrEnQ{NpSP92;5h5rWQ5zYQH?#Y``hM9e6byLX5hR zTIeEs67{wK&0}+EdwX9xUB^&$rw7Znaf+HEv}xQ4{L>lsF6s8oaI?2q;|b6jf_a-dNA0jTvM#x7GiHJ*_D4^o=t}LsLRn^ z$EP)+35JHP_SD)*NzLefo2(LbE_r)gyv(^6bqVUWYhR)4q-~$$waJxW;jTF;G*Xk% z#$qTwnls8Zr!tSBTaADol>FtmP8r9SnAq&K2vtKse+cut4|j|4adJrHbX5Nj8c+GeZZd9R805t_%=Bb&)nZi62 zHIzoVWSpc5R*ZnT8ikOt3{?-5fHm4PT=292njn3!P9a}0DY)&ymS!ow8h{7wprN(}~e!fZ}z{Y2|b4SYXlI>`fbSo^fX@gFVDVNwF^=>^NJ zuPsk?ivVGct2I;jSCoD>A_mrAi^9GS??jr4brTL3ht_Ui$k^YZx>F8S*X=H51XG5vKURSCsXG)I6pUii+trit$N<)BfH}`&n9k_1RK?hjp6(76Pl=CsR|V{?JpUR z*Y@^Jl**S_&L3umuhvDwr_gFK!MCA8f#uP+7V-h8kP@j*iykP%KP_eR!Ia8T`QN|! z{%H3sx+v?mV2Ws6wAvHC`37Fi8D9`7Nnr+z7I~JTbgNh}G|Z%@n}(tf@SM5x%^gU5zHU&qbZd?XDXm!N<$o$ZrY%qZq?{8I;&vGpt|;&x%~S|4*7>wuh(* zNgw=rW|Y0_2dN1B@H9F-{uUz=y$#9H>1+C$1gED*7@i)##pviH41$xlL3Hdh9^zyF zKk8rC$FIL@pS799c(Ej!Ca&ZE?z)OfM80`60d6cyHYzkDE{L$+lcpJBWaCq|ijcZ->&fUX+qId3feWzFEt2xWQbp8*8Q|)fsI1v1=rx+A2 zA_uC|-*((2O`U5FX&f}c9S$&H(A3J}l_CXlWyd)5f$|)Au>&PpvE$~Bzog}U%+3r? z&&s@vqLbqY$9TwX^r?Kii{}8mqKz;%i!!-(WcPnkgL3Z ze!}VBqPLx7@5$X5}dt7HG0`LhA~W~b-%{f>==&9 z5Wk?o=@=jTJJ;cG?(7%k!Z=GI{_hLy~D;ipBW$3l1 z_E8STUEL1Y+IQRfSB5*NpWS`En@^@UzjVWEPJFfS?Bcy}f{*xcAbWpH(a-2#wOHS8 z+cpq>*I#iB+*-=C7=3l>G=-NA1=^u&gYF?o1DZNnL?}`tDZ9av|9v1O%CaOo?z-xw zj&wi2`*?TktJg(V1i|DufMdAKn1+;b1g4=N%79G4WC)+{^Hhp^C7fMQJi(U`;e6OdG`(rNIQ&%w%9}CR&z?B2byQh$lfH zNRGN7w8`jtF7rYP6y|y^g_azz|142k_oqQn>V1M2-zXMV^7@@v5zdk~YFXweO!MK? z0_!sWgoQ$l!e}5sGs!5z-?bx;<~b=%Ce^`+P6En_)-ZSGeAs~B42nd9e-Zo>000z< zt%yM&N|AdQRWaz(t#1n{IgIj>8>W#XH86veaE;qy7HhGDQK4jBSeY@L!FX5(m6GZM zYE71CCBG0}A{;%}N0wu(0&aX7IAsTxeU9P0Ad2MP%+NFP_`zki0TYY_qVMcKmNBJ9 z)iGHCFl@3$1)NFFv^xM~MHnXwj&DH{ zz1UwFiY+nQLeaRRq)LpdZ^b4FphUm|Yd&MrHF(~Ra&N*K&4-QSu-Av8DQH6sqa7IP z{I@$+Tk(fTFo^b3rCw)fGOXsD$B8Ix1G~TYOt+zkrmidREq^cHessTE3fPe9o}Ef{ zFcm*KmC)6|c9*@eRQC5Waeo;9i9B_P+k@!|(lpa+dN--K2?u#S;%r9sRD+f4 z65Yfudpa4hTR|Os!3o7o@&pxx&voQ1T0dJ>e+aFM)=UnE2K5ujb!PgO>Q6p!e!Xb( zvu|n&lyKK?y9u<$#6atGBHirzIk$v6ooB~+bfQeZ(OS}tSpA|sMY;eqX@;Z99ww39 zX&~H;{H=iFt&rlzYj>dAX}{HAJKr7ICos>D-Z=&m&}i2kyJ1tA=cHzLPs{BQg~H`+ zP7KY$@#O2(*ZAg_@bxryKceH&WE?|Hya<}iZ0uGAbu<(XZ0#YtR_;(2iV2g3U053Y zTGQP>tqr6pmzSS6jKOaOmkEaBcrg5q-A6#KaUhQY%U8|2n~ss7Uo}sjOU3fsay%S_ zE0qcf&05Uy{)mRr9n8*PwAzJpSLbSUcLOspisj)Z^VJ}fd*W|1u9bZ!>HU#A+6~1$ zQm9LAhAeJdrEMJW-XT%F+XaWiz20GGv)P^?y1`so*sPa+Kz+0Z5cA%zsVs#Fz1axP z;N|~tNXD1dEMBn$6MrvZ8c4oi%Tkv51gZ<0&Sc3GyX;AU_EKbTa3|v!SY98BBajM? z#&MH+bm&Q|_)E{i{0Qezw1E&4n7rssHtK zXMbWI`=zKK#9q_28ZDK*TZaSc?Yp?&wjP6j0gY2zPunmQe$TJ?gesb#j=odMKpjX( z6WV~sYL)ArG!~AX*$$-}{qK{V3uy}5{1U}E=ewPg^UpKt1#4O=i&?fYKy`5d(JzRUJpweZ`VZ$s|!a5_C zrK1%#!ewyYiPfdy1g=*>@i>YU%gGjuw~ppBofle>a`Ty1R!f?HFUflPiBBcSzNf;F zCFLv_wq(aj<7ZvfL~+SVmud6gaf~A_nYEZ{m5SPM!whF|LK0P z^}4mmzE61u&ZJ~ZNxh6ZDIaK#la5Q%2Jv1(od~O?dJ_~6Dhr9O3zXNS+zo^4gmrVA)@1niey5`nC`bD22_TUK$L+eFY-nh~-YATb-fu`_pU^<!hN-r=xfsUcopKZ4jbYqX300Nq*NZ`(Eye)nH-E4)|^TnF959@08) z;HGPVw%gjEPX-K{KG{ShQYESU*wX)fASqFlEXz*PVZi)i%RU~z`|kLWJinS|o2=J6 zIqtzRTyB_xm?a2IK}Mto5rf_!e7Q?w!S5g=^op!;+yiSL(ILDgONJc&5E?mUDB)FX zzj>|Pm@yUUSz-THNb4@nlLV;9?j&1pG!VXmgi+)Qf$><#lxQaS2(ko;LbzslW5<1X zKfCz!<-)kKDD{SDxFHHwOlirMxyBXTFueh7B2glj6oHDBMSRlhagw6S2=x)oXClo6 zN3Q2H!IemG{%4NLO#iai%T<@*#Vy6mSkB+`Ym%_lxm@Qda_xV3d9a5MV$EnUu6J^5 z8T*WxM1@={AW$MJ>3>afy%F-@6rA*ELX`4;dVlu-05Zw0iAFFK@-_6oc>=;;r;Kap z6Rk1Lw1NrnJW1*%OCb{IZ`f*u93~KxMB#3Www9}AnMAznPrnJl&vmZ z^o9l-yyd|&0M=#sK)AX=X*^2Qx#YlN?;DV7lnS`e8^+hgR3|Rxe3>u`an7klwpdWX zm6kcxQM2Gi2f=cz53P4sz?KNJ;-8;+1}4oq4vZ>K^=yCNO2B6utFn=Pt5#n}d~4L@ z@`{G1ML-R#1%nO9hhV5@V=t07&b$KB#%w3J1Nphxpx&UYDng>lgwb9?q$Qk*1RliQ-TfT(cru!!^<2c zoC|FbZ^ttp3@hpT-fI?@->3U!njTbEA&ov~1stEXQI%g|HaWL<3l3cH=gnB^8SXB> z^6>D0GAxH2R*w7O78~Hs*q@{rjVN82m5BDJ#@6x54}4&Xe>03+lO#t4adC97SJ=J{ z=sdLkYCV~ZO_@e%jz`S^XaL#;({wWPiEjzU51&?_qrqSZr@={WN9GTS^YN;)FW+rM zd6w*PHb>PZFmt9BL`rfOmBvk3DZa0$W$IihLHz)jg7vUSQLZs^pRe#vd5ckD;2wjS zPTPM7Qq)YZ3j|^g(GVtQCAvVc0275)I}6~9D@`~>5ySjal53R8u(K6s%Pb|+(a=g| z6>@#3c@;yP#f;DmHlhd%t}=q@bOJVETF8YK^<<3P2|v(d8)7rsj!3Hdg}R#8rLI{@ zez|yG!_L)iO5e}wX)_z*k5GVFpxRS2O^XK9<7wS6JJ<1JBPg9*hU0A;M47Sve8PB^ zYaj|paoe};i{kiLfF~UM+7&TSA>A&qGS?lIuh(u1dD@|}omo%oR`qnL#|t=vxpBVW zdAcBSt)eac(2}j!C>JZ!lAy$$NQw@_33VNf!3YMwj(;(429XK(+0QB(KC@jJVTj{* zo;hXsE#2X2WVyCj0dKQ}QKpkSyX=vy#)A@1)ir%lt%o@j$yOdJ{dkz zBwQJ}%z466X4<#=M;>ze+SQQ-7F=7lr!vLC+|a#udUAVSjEd^L(XOJe)W}bHbQC*X+fEXJIVIfX!OLYting3NSOYCO%tG1+BTLNe7ADn3)D{cIuf^5YG!Ihk zPaaQHsdfc&&&{&d`woUZNy?tcDEV=W?yRMNwSzUY!nuoqGI73t;Sorok(#;>`X27C zW^WbHw{OiBc;U`A3x+VKBImle#l)P;9STP`XHEsIdg zKA`O?GVV!Y;n+*l?f6?q&L?Vsb&arULH$1nBg*0*v+ zqcx;;Yp=?RA0Oh!RDo<~K~GF3 zC~I3&*YCe8jmr}^-oP|k54XT3`6#DCAkG5m;_xtf{!1nG7{SmAiy0M~ zTJO_W3)|kqj~s~+QvYRK631I$VK`);i)!I8>f*`^rI~z|$^i<4EPd!>G)7>b2Ns?~ z9v?xLJ`B*CUtaY3*xkj>|KPmC>XkTrpYr=>l2JG1c? zH*|Sr3}{#GJg z#u7ZQsz4dMa3~n&0-yJRIZc{o-HZ14PiZira@Y$yUg0Co^y~EyA`#vel zH}=R!Y{TBp1!yz|SB4G@##gX)oSjgA#v&A=QJux*&2TD@9m&&?_)+O;%dV0+8nZ(E z3`u9Gtw`4t(xRn_I+nPb?_hQ;>0WYlM0(V=Y5h;7k57;stD6m2-g*O8|3f>t?78VX zkKUu`7~3gClgT9Rq8$b*0Yg7mmPJ*T&Iih(_zG6enSpZMuCf6VST-M@EmCi4x$}g&fKN&Q1x5_X&EZNPu4IAo zK(Dow5yM%M?4@18mtp=xo?c)b!-+reP5lL(RNHRbFbsY7R}h1NT#(e;YtmebE***u zY0+%$Lt6}rOxwZ~$;e$9`;(Y&VYEHN*5^!4yfILmujMl4p4kMBM)p(u9=tritTU0=s3u_luWKB?coQpps;H4{iI zobsJw+wHX2PFyj^o;l@2YgjOmax8(LjqP>mO zH0|vB($!B!AaDaU{a1KXG;^}Tb_XVdy~cYD#niyn)Aq;pjy8H#i-{t*U|SE@YLa60 z##Ve{rgKhq)l2rS-_MZe@0u#mCD!o!Vs>*oySZ->m9ATl_wOJ73XsHc0qiXbA2A`8 zoAN1mQJS6~>P}5?hs{2idbf<)HToYaEW}&C`G^X@4;z1xs(E+$p1h)-YA)R6lL>sN zX5%UN1FcqFZ<{a_edkx)NqK-OZMN6+V_m=0XLEZX|tCk!Nz zwISWY6E^qUbI#=mbM!HrW}Y|L^I#7irouujB!aMzF+-qZpee%rB8|0LK*snJOYp!0 zSMT!xu2?8g!AKJ-F3`YH>~>CVi8ok-{q9$v|N8ygeGPj4?8+s zqs-8ZM@A`XmWXX)3162K}*;=DL2Rn8@Vg#Q}z>%;e9ZK#-lYQl`n^yKizCK+y+Zc&@Y?r$d^=`obAAZR4o&`k#3QGpb51Vv?xO(afY%(PYYEc zHU>0l0)k;Nj+b+|M%2db!fpLgjQlceQN8ZAyn;4R8*K}_!URJX@2zaNaJz85Jj?vo z&Nk@9^{<%z0X%zu0IilyZ`&{ohVT9rJoJzhL9<=^v1Th;tXQ!P=ngyd!bo&1 zM5a86N*ZkY?}KE!CYR zN&s1dHHtV87%rW!QH`^PB2Z3e;(1Xp))X34cNN{7xw>&oEH`J4gR^vVEjvp`sU?~DTa+dWgft2j*@k@R z>tA4$%6a%g8^iF)v4-Py`n`0l7poeL?aQARhJ}2Yy_42u|5*Gk006RG`s6m%C;`I)G=jIx0mWfTs|x zz!LrVez#IDhhQM@oqr34EHs8-i=NXic>{({YQ$o&69Uw=W0RCd6C|Ax_6lQ8e*=O} zY6OJf@-=r&%I*(fMX1S#8FbIyZHEry$FNSuzaoYEH4htLSl?YJeZ~w<`yovwru}mQ z9Ic(fsX}WL6YOo!8ja$7H#t3o+T4z?srSqU1 zm!`Y89%twx2MCtV+4~I>A8C4+haYT-s+>{u%%y6B2b{a@(9VZhTkZdA_edAC2Bxhw zOp`Dje@JP2l_U%c>?s#HIlmv+pVxw3Lf!z4cPpQ+YnT5PjP~wzvUE#O<1L)PyP@-; zH^_!;xG-!&H4O7=h*o?uJu3bd{{Y=P+fo}zvhRFFdV`tn zo@l`KIs33Og25g%Gf+=-pOBJ-L`&5+>I%|K##)&u|xL>kSx9;kTkTCY% z^kAd9<~_4AQ@i`+q}B)KJH+};4a zK1?EbOGXqKd?+Ncgd&ICOpkmo%%1I}nC{v2Zws!E%Vv2FNl`919Zw_>mO@SwWCDS* ztl(23X~AMBb0h-cj^bSJJ3e}IcyfNIj?z%d36U@-0#Yg@r=yv~6y{V;Kq^c`G2;nB zQluK?+h=Y*$3zj&46eEL^DeyLbUMZK^(@PfuXmqg?bVw}K@)Ue*WXsKj9Sa^=*{bH zxIjLojO4GBMbswCAO|TXg%$yrXu%MWSzG2zqc$7x@s!ZqS;w7?Bqu_+)68LbFOj9f!l1`C{;~l8P|ij2H`i@$(6j&6rjNxVh0Z;14Cj5|hgUxwogKcpI6eFG&H3SngR_H+(=!+jhY(y{ z1+WkQ4Fh-?J7Bi<#wdS5J}Ly2i!h2IBe}pBlDuHpSXDWLFp6sMc6Q)b%F<#kAep8x z=cE*%$Sr^Ra3skD(-`IxloQlq&k+bm$cj8K=9G;gnGi0Z z;J^yGT~oyzGK5gm!5$q(kp}fwH5o>*KLCMwHW;)dxkKGrK41t_%0heo7KD_^2p*`^D2D?cyRe|Z=H)3~Z zFK`C0vJ-=WLHU9cno~A@O_E!Y6EUd>7e(=!M+?bs6tF~w!7zxy_=CYft7+)Iahw)+ z_=9n&w5XWYv|e#dn#hOX`fIXAm=Nkeo;h3pj?Brakd%!>YxXclg`Zf>oC~~{4c@q| zFtjFVO3E-}5W1NNhI=(B;XC+71aY-5ioM&r=Blzq-kPsxOcZXt{$PE&tZBlRTJVMe zzpjzZ3LQH@ZGx36tatsTA-OH;)zmk%ujH*h|IzaRq-E2HeF(b2Fz8Y13?x{e{Xsh+R`oAw=*JeqALOcon;Pj^;h9DacM>$Du71(2r z$kd>U9CS?ZWJr8@{zCP`)!Kh3?3n zC1=H4gx(ePBuAF22%{SA`q!1tv$p?}g46#km?XJD_Om6gKIq~r5v{r)tRGkZs$^A> zn0m_I;$la~0v>(xMVrWnDhA-i91_A{grHIuMK*BL-$8I--b6t?bQ#>@B2ZCSpf3on z>p7iEXe4GGUXSN%iWy~8{dr79)4zXg-T~X&+nWmU4u35_U@RhmK{@T$9r7xM=fBn6X{wP!wcAXEXOp_c_V zS_xCdIhBI9yJENuk|Gx+VVd7B{WnAhXcazvKFH6!pmsUPa-vwLU|m+*34>oWcf_Qa z<*AZs!i))UX_Z*z!nE5n{DTeyw}a>*a2s^H#R}l&o{BEXjH}F5ch#qkp0`a|ZNx6s zao0`#Zz~KDx7F*|rG32=lMQ6++*3mGTwSBZjiy?Y%w#lxDZR(EtxoP#D=#kB9D3Ke zYy5|mo7IN!7k5`!U+X_~ta)%xB5~WjWhEKj(KJQYM~tt4bK{lPK-GfbMFjr@awMiY z(?blCIIP7&!#U8yEg~!jLXo7e{?jWGD*)Birye-112^*`Hf-w3vq5J%%OyR>924nM zq)Rxftd80V&xXSq#`eqCyr-*dM3&PsbfB~~zJ|zdyL3a>u)kl;_gesmuPMS=X=_&G z8bqzg@sP%u{aVo8E5Zm(n9!%vBZbYJ`1AnRm7C5b;WE@UrYTXW?&nK_G_O9T2<%60 zUL7k`r*xA#-sY`6p|AXMC>*4B%8wW%gv3(S`IcZYM(INvZS4`eTkrgGK)t1mib=RO z7@1waG#~cTo0>6!RrSTA?Ft3&Fss4#fnSeogWcr5s|77(e?!T2-A*ZaZw>0>M+n|6 zO5}kqM~zV;y90~(+l8g_VV|Cc=7PD4mI!Cc5Kqw(}v>zEEFl>xBWo;SD!(0wxClN z?h#Ibjbid(KqYb_u^aqLoR+yR9RGxio>3jVwKu7-Yzq3!QI5N6YiyV9!KNE;FK6@R7;V1M6u;NG^1W@er+ zF-OlSHBZuhv;lu?7RJyULOvQ>Z-Xs_doRNO^HQk#sr?5-__ZAmaK7^gp9eE9j|=H&G3{lRfNe9$j-DsJ3S zUZ+4u3B7_8qx6Qp}j_3)US@Znat2jg9;D+pFd)RgdjO(bC}l zOarg7x61Zbd8zq?CKH&FTNI#7sg6`qVMs< zyVY!;uhK6DeUNRETlJRrGwK7AUTcT+b=TGk=hbcl+;1Cj*BYXV-Hj=aVXnc-wB>1l z27@u0-pwDXRC5^E=J+kWVQZ|qM*Wc69Hawnl_@ouf0KR>gI!ex(_J0Xt`ZU8Kb9{@qw%5{UY9mm4=;hRW(wD7u?z6w|rB>Tb?_qE;X*P>ep=y?(AJUj^ z2P@$124aI+Q$2|=or*&hWeCZg9_1v#@an_64>yOW$1zxy>Q}%FWikn)D2A<Mq49X zonw|^L{3ANchaxe4tQ7SUEUw8G!X2=NqHXr)hC5_3N{X-z z%2L-a)YyoxIps?M$88UWS4|YG+p?rfcNP6sQp&Gh6A^9w;zNrl+X64Q8tJ+-7ff3x z_9Najf|EBRmr%;z`q33Msuf68)^iH7b|e+B2PZlFEUwCmvX|M6CCnL8(u~bzs4RuG z(>RtKu0&H{4%M3H_n7uoqjQ*WP4HF*a~*>I&V{ZBn*VM$z@LP_^S)?fvaz@9t9?(4 zaQs;A5u1qXvmLB^tbJXK9u}kj5xhw>5v6nv1yhjGM3Of}Y`E-n_ZMAkJs3eL1s(eFphKTj`R`*q8Aw@JK28*6{_Imk z4WXVm8Z2fqnc}Rrye?=>B&b<`o%FT;1AMzenKQo0;bs^21Mc(gt3T=ZB~(;o?lS#2 zAc(|}!*2WNJLP-w@TubbiNV=9>C5Z=u~*DIA^o&-@3(;q#e=Z(v5=P1J#biEp-@0N zarz)VDutv!6>@!TXCZ|cj*~$9dy!Jmo6u{ue2NV(<8g{$sStKjBXwt z&$F>gV8tP;T@VGdENCBJH;;_^5SkMO##)|&{)J63h(R6k%D*VkDyIrY@jcN-E5&d@ zPEGXYT*S4%I%MZ!8t3bNh4<>B#3nzECW{KpN@j`X^5Ksx4E#UUG+%}_FUAH z)RK)|DHNApMx;&j+2deev^guo1HjLD$g@A=$dKG5*K$07@vIV6vA7U0V}fSIu-qX_ z@YWfqk6F#$O0$U8iUFSzhPObE-RDEbe)?=9q*QGpQ-@XA_mn=J>Bj_)Uik{*8y@mI zM)Q-zNSD~~)5WryfxAhc_0$i?r`&4Z8yPaxNJnR5Z%@Axzw`Hh519)yBP$~kALSn<0lT7EoxNG-bcVc@Cm)ZsurhZ8ZlKYT&vi)E9w`xvbmK zs8w8`TEsb~vD>1*%pxCL^ES8s;nL99IZsm0`2|R7i3sl1pOQ?aC z0wJr5IW?39Po!}5D_p)fx1u_wgFA+$$W+C))NYHQFtOxCm13U$8+7WH$WFujbCMpY zq9I##a|Ie3zovpU2)vR!eiB~qp;vY8r44kFB`+*4_7mOCkHVg8<|o}{?-*KO+X600 zIe4ItNDqN;+=@w1)n{o9UhKG)=BUEn)3i*)+>^eeQPY1BP-Ca%LcZJz4x z`G*6TNGhXZAq_m7fjeEgn#Ort&R87JUGp5B0iI0y8>tx#IUuqU^4^LzWT7&QT=AvB zHY6(+9UecdI)UsM@#|BEZL7ycfY;dbpnns(*vD@qah8?(3Q$q?D|@^`<`WnV6ZiZN zgg3sLQX=2}KL{M}zng;O)|7vSDE>x-niA)C2V@9o|>*6DF~zMS;|gn5T|NoUC}OnGMmR}ua2EZVp7o>MdL>6^A`Sr z7rEKnjxdihU&wf(xQT7<{FkiId@)4lQB~t{r%PgHXySv=Yfm1IjYTVz5!Zen~oWbux|H^lR)oXM_K($xjQF z2r8$(d7QUy8DAoBwp9gnZlX{fWkag1t|Od5i%|J|NzUdI%*Ca>Ag!IwN@sT*etXtz zBTFoF&FN*2`dvcFVeljT@=2>eZvJ%ZR8W>Ia*%J5lXn#UVY0@k-I3`6z`p7tgeV-Z zDubDtyHr%$A_dF0%(*I7uJuMZrj*q~z|_(RKR;mPTL`ixP) z_NjrdkbgY=O=3}s!`u8eGhhYvx3JJx!ITH7#)I&-nA!9Iz*c5Y zsjqbY0qJbR-)EaDdOK1Uu`xh|tDsz%w_fFp7Ok3vA=rIMcfbfIjtG zpDjwzu@t~kZ1#OhQ9=Z>G6)ne;L&o@vU}4uViHyy*4^zEPh<*a!#aYlHjkr;VotLg z)sS(oAldBJSoFfWwjfH3$geTPbnF22vXc#*1{pQ=^I+v5I3x>zx-!Sxufil?6*&jc z+bS}NRi5gToSLK{+wz>O3T|L2Bwb?5ggZLfNT@}`N!q^Omb^B_wt&K)#CCfS zggs2My8#ETo-HR_EaQ8c1|ZVu1flA+K|{~|?6H^ncP0L{-!3GeqRbNJr_KFVh5G|` zd*84DcWGUa1glyAYVp;1;|1Vo#8lD9_)h5Qmd0Z$+rt|Za^TCcwVuCMyTRm!QCuOS zQFHVSDqb<(zVZjZ+)oJszobJ!mji~;$;YLmis(wnzADrf{{|78WSd0PnMwUj(p>aX zj!IEL`|)EwPp`5DwvA`HYlOllie$ZIHi*zD zbG_68Dwsjw;#tU(+e{tL%LD@5B=eYY`9{(blKBNTG?wr0GB_ALoNPO5(553=K61<1 zT7W^ou7ELjpM+WLo=Roqcwfdj=R)4FNj)e9c7zx%@3e|#2kcI&Us1u>@CXiAU>|wM zzYHhvjLT-rh?$w;Fdg_Ey7TxW-q=Ax-{`_ls8PZ{Tc&|Enl@hVOOI<9uOr z0B(hd%NK(hJSB#O&d?CIB1+gLtd@Tb^ti!29D!8GrzYozN`Rn|a`DhR3I%F@aN zV>GPSX?$u?_|v|rj6zrToPk;dO2JUdCa4eJI$Bg+cBCN1rz{534>j`3TX$=2Z}M)s zmFLcjJDQg*k%twyKJWDeG!RpOu?s32JAx#fK<(qjmIEWrPiw`g3gxo&>QywA2?>T2j=%X8&|(DW8s$_IDU{}V9!w?XTG z1-|P-brPsg1Tx|Ede8>Q_Akt=NsF_GM1Y=Dv=sqznS;nQ#?bdmz+x!M&{h>q^C+vW z2~MbNYL8MTg*XW*L{{&Y2qW@3yjcijmmeh`3&@1FI$K8|NPLPnfLcpEf2w0A@?Wp5werM4(@SflFKxiFd=jS7{%Q_< zWb*}HyG1`A^W`SFPjHh}*3VA)3!ra%a&zr!E`Nw5?)J2@SBFMwsWd8`coM&f*@nfS z*ta+UfI#R}f0rn>26#pCiYD?o!LFEd1k=XTxEZLhwKAKMn-6SPcujKQK^&C`(^$VF za?G98_I1BiFLrYeD|&v+9=m_0ZJ~wvkyYKvl}TvZc0Nlh0|@e59+_$|Zca?D9nds4~>XL}AB zQ!qH$H6|mpsY@duDF!S)@M>VSNT}}O*);6aVb!|wD3!UZ-i4i}=Rv_@GsA2(dCH6J zd9as?xb%A-8PqtR*-0XE#m@nIOoz{;r_x)mPw+Arolkw1zyf{xhPf@y%o_3P7e6M? z5vHivTUjNu3e3kfuRyOYmW1c0OYwUDpu2O{14c_HUz)KR@-0@NhPOP(o7zGqPY&yW zhRB?TC&<+fiPYYC%1du{Z&dd5UR^0a{j6yTitL>x)|?@v6h6&*ey59!Cp!b=P(SU5 z@b?1P>FdiHBZ~V4@_We){_C(l4nSO*|$ zGbML{JQs8DBdQLuZ7C5>v@0xLEI6x+S8`8R=!3=<&sX0BEG2KYWN)n-mrw;PpD*tJ{t^r_^uDi zV?IzJ@-{&~5lp#wxuACZ)VRRA>G}dU>xmo~e&z43dm1Z=WR`N;3+$ekur<6ck!kKcwn*xf>p6izJg(mU>@(6&uP3^jF;0&s4v6XzpM zGd5u3i7^D;mhO}Z$|`PfaHw52@!Din@Ntu-14{fTDtflE^>CK(|0%djR70v9p5g`* z(fz4UHF6^oLV;J=QMDB8O!rjZ-+n*Y!81Jt|L*WIm{7>Le8h%`PmIS%O2O}~YX>(k zLJo;j#kftLGrGH66#PU60H|KKP>53%8S3=29?zMqUcnV8b!Dyu965@ow9UnAFn{VO zc>k1U%GhQoPcx*{lu!W;(HU{@@8%7vYI-+3D)`!?(;_U)QO@MXcp|zR?@ozt}ZsZRW`DBauE3FlXQ8i0scW$)w<5p2*T=3)tmApLD5I z_Zfh_N`ee+VtjO$3iEy^D=-r(x7^19u3eh zd96jPKqza!h5sLGHI`ttcD4+d$wSoa=H>QsySFu0GgHrY@e~8=VFv(LM+YAc!jCqZ zMk0qG7e%LD9G@ULiuN;k?6wMQihB#FRhP~O;TrKj?gr8S+zpPfZ4$CjACyJdT1li3 zcFELDX(J#~A#va_^LXIsmW3mKg1yR6bdM60ZWWUQ$ag9ka7&R(l006roN zGQ)Xv>isFX@eb7VL2L6}vT69ew4;ucKlhjRGmK;~R1=7OTYGF&a+0@>5ltFsY1=0W zLJKp>5x^_N z*-3^9f(wI7CadNxLQoPcy^6+!1ZAy^@m|2iR2=>DToR3j!XGHEsF67l)1mCsv+HEo zrggm^9FC##&LZS?Aci1LM-5O5P;qKjH>bHPrI$n-(erqDzP9)BWDM#m+r(5c(dJjr zQx@4iZqv-&HdHg?4?+~{JKURk{q9CSXTdg+YCv8mcw-ickk>3-l58xG!9zfd#!i=@ zbSN`RMIOX@F!a zi|8m45nGoSDM#^ZXt-LkRwy{+Olrj#P)?_0Da1x`+`0@=p}Ac{dRAl#nX=Zodig`> zra)j7e8ssxH)_Z|SXur^2^m83D18APvV;*xb0sBlbvRrsyRzYooMZCiiXVweK{4(R z7@aN>I6oI0HG)$+xb|wq(mcCd%JIQd@Y=ZcTwEPo)y^P1OvgXqtlEPHu}#pCLuciy znTA_)B5b)TeCkYGD`GO%I4-wPKXoOnJnqHKP>=$M#TTZvR*n+0yqejFsAdc)-+#;; zMY_mn3_|T{s8nnnHEqBV;CkuEyaPedIrVk5Ajh_hp)y=?zO~tzzf$8ele0*m`FBH= zuJNt((uV6Y2ZM@jFFtu@9PrpzXP)lNfzm@!$P&tKDmf&yg4qSP<^ zN7OIhA06t0OXV?{6VS;9v1$@%h^77dso0 z>h-Rhyl9Rb!ng6Sb-_~B#*B)(Y`Uf{jX?G2tDv&*Y6!wf3$t30gkds%88nu#jO~vV zfw7IXo6!x_-4xo8N41L%0?-Yv*#PIhqyzpP(%^3wG+9_l2P+yLtHEvTS?mR}vITTj zk90c202m)UN!E~qlB!_CE7oAez%$qr&7jv!d6bF#s<|JBR{E!p^3HMqEy4nVxn>DB z&bJS>OBm;dhWC;2jrEXK*clgc{jM`?srhV zPR%7$>S6YQ=K2yQ(Wfsm@!37x6H4BU5Dk+M{QZ>zM9vke9Frb)On;Slgpx_@;qcoI z!B);p>B#k7ZEi2z{aW(VG%{-uMoc`U6jk3`775)aL?U}OUxX!6d17Kg)CrCRM3Al* zxOa@cy;+jwt!lcR5#rTyTAhdim0J@m>kk64R`}Jpx~NPS8mGjMlppf;{?99{iiU1! zx^gbYIk6;(WP}PF0b{Wc0KI+NIc)BHhQc6P&}O^faFg7q zPQu9jN*s4M?rjh&Z2sTiAHv75cs%LpeJgC~K4S02Ca`ln>G=`QY;6O4cM~3u5b^vz?swM8nkjK0euk* ztyd=o@pIq_0mALYeMiQF=}P%wN_6DO=dEKLq?I8ZF?O(}5beVpgq8vH!;`zB!o14l z$sZ#uH7Ck6E+N_&(-(th@U2#^b>rg2GC2N2oi5O*sU4=-q}0vE&`+fG%euEzqlT ze+P>Q%EKQ=2PszfFK5|loMimi&71`-eJ0q_0uez&>q4kkdH`C8161RwZ4BnZuz?s-4a$_khHc1mNMjB-7YG{KP&M zr5dUOuI+!)22CiD)nYiTuR{*i9vZQ}m{elu=N;URWWd(>MpzX5uVsvmaVSRde*O4w;9X%ADaPtVYm@n(#a+FH-qYH9*Op) zed(TURkQO0)ID<<*7$ezq!W$N`t_>xF&VE!gH|2v?d7psI*+*mb~(Li5#q)yEX;dy zM$LU4Pz-k{Fx6Ue_{7_z0;1Y|LV&{Eys-;1WV1R3`d+5gFCb$eBS`t zc<*%DkW?X=1E2o~mkdd`%&skkrx=jIv0~BR?m26oPxuY?+2uQPs{m&>kP=T;hq2{)p-8*W;mX^-kMchrGfQb&x>x z@WON7-a|2ggm43F~Nr^aT!8Jedo z&;g=ZSt7&hDP_E~o)z=mX)yde3$Q{pa_$%y0MxUe-Uk{uCc_gIKb~=KL&C&X_5x|0 zqv|00`Jnk;yo5!vBOFl>E@yq=B}R*$b~A8N8S89@Sq=G5!6kP%kn~U_&<1~4(@Y- z6UVJvL1C{q2Ku#i+2|%6GcQt$1VrR&>8WU+=dsWm*2~QC5^xiUy(4{j3r9?UjA8?q zg$n#Jvx$bDGf)ti#ZSY*8?|+&{r^L_j%t%wz$YMAPD(=iJcx!|v0hOB?+_)wLp*W)H-t^N z2=EGrc*IoQ<_OMtPkS^)xFBMKtqa0UN)SC~V_gqijJM1y-T~$y@^MeFJA`7Y6D{4W>j0Mk~XH26b9BW&l*)BNF4BZPo2IWXQ? zw9D$NvTD?BGdQ{F{8$WEl-TzlJQ#B01BnA91)GJ^!D78E=!!-MEeN-P zPYyX;j09M3CD-`JaH0b)ALj=W(~oXM}5!5o%j}8S~{^8Nx5i2!GZJr%PN>{ zBq>Ec$ubg5A#j@C7NWfr8pX(XmHk+>hXBAY^KG++?gawRGa%b$6i8iP8n!Wc-u7Yc z(VGmN!grR_uy;w5RKo(FZzF)1G>Jd3(%y2E*pCHRlm)oo$rqZBme(v>3-Ptg_@;_{ zh8%^=u~*50sW>?QFS50(_a;k*WRsFc6Ea1;M1uvo8YLjP)1v$qrTHRaStw zMYG@J>bA4$@HOJ;%1$$j2?~Slr^b?If#RrTW&{m&8Y1Hpr0skpE`&kNqAa@<1`fud zFCFyr7OEdB&qpwMg`z^Bi)si1iBexDzYE0c^&^or^cJ=X;k=vBk>@fuS}WXJ=o#u-Y^5fO=HUWE?~_IEBzt_%FcdI9W+xe(0f_ zH)Qt5S(>4J_ltjlI3RHQODN$D=A3j>(CJe8@;x1`9oj{B852rqgMl+TxSaVa*4Vc%(pgC_1AmMkf1`Zza{?T-ue!WMryZEe^+ zq`v$RxL1%SrT=GU(93m6WxBE-TW&gaA*0I`Zhxw62S*FG42B zyQ0%Ik}upw+DY8@9#v6?rxGJGil{Us-<-5&sK24z7l${~N}Q5|_hO21iuYn^#mfQV zx7k?bt|ykkkw0QOqmd$KRN)xNoW7}Y8I(l+TC9;NHc?X=%riACj_oZp;=>}^b6O?mbtmxC{MAh8f*Lm+>GE$ zbZ+zmQs38#2QBmFN(MD28+W<6|EEwa7Z-e6#7DEVArLZ+aM6sF%)n~~r~_}VgNl)B z!Wawi0Fw*$PF|JB0XiRSRIpO0ty#M=1dYBD_n8cXcgW(V7g*h=_XEW}2h5S2vuv(g z=2cw?a4{=>SB|P$6&quR#Wz$Su!YhrA{~d$%2+H+LsqU3no8a#YJ@oy$bDP750QuM zw+JhME@=K%8i?g;$DsxnGh&!IQ*}^E#fzJ?WSX+*z*PzM_03Os-3F&gqN4l&Smw@Fr!29jm9=yc zYv`qv^l^-=O{PEf6e6^zI&^0`)YjB=#&-Ye)M$2MgE83^KgiO_K=`?}z(RVjoX@6p zh59Y_pxyYW`RxMPPBrg>;N5f_lfFjuYQGJ_(l64L_C{X;C&@?$n+Kwi3KwINRm}w zQE^3c%Z@rNs-fqXT5ipZO}b|Oyw;(3LQ2pobKBV|++lLV4AfI-RPY(%yy^y&7Fg@? z-=Kp^n>#C!WHvLtu@xn=Dju>ctPax}_lG_JKq3(Y0^;i^!fZ)z!OqAH%ZHjHlup?8 zP>p!l@OTDNf3&T{n)_@&$WyOv_AvFfY4Q@SJ~qbQ7V}tpREcsYNa3krwE0ICVb7vb+0MtK!9~k! z5*fX?5U4MK$|MhD7 zu9U34FD8u7RcCJ-b2uC>o|Kl^8jzM9KJzb{;aM2(0Rch-jJqJcH0%K28OIY)tJ`aG zy1L65S=9abcJv0!=nf{fs$l95h6(AKixYCss?wJ)>lpT^92iAVkRbF+=EcwSw+K-r zeKcw0AJ0rP8Sl-w#iV{n#&T-3ht?3K$DJXh4+|QFXc0@f))+uGgyZ)v^XTKVvu1}}N#6KE2Nj+3V9vwe+t_qThfvHc)jSfs21}6bFWr4Md4agg`%F!65qIA`A+Ua~P?ja{@8;BX0WZBG3?p^UFf# zwevwN-nYC01fCQB85T+)N85aGEY<>WKw+*aGQ{(d?i zo&j8}lKq&KIGZP;Rvsw#mMA{m7-|WhwrX(Fvq--oLU|HPP@iNCb9$8LM)&Hp0B41LY#ocGrDch+8v4_&0L)k=j7Fd~Cu8IaXF*K%w4 zpb9$6H!Hy+rTb%Jl>f?LZd~Ld)0?+Zytg0&MMBaf*He|1qo_%hduVWJ%+A%XMFrne zZvb?rtdxei|23iPfj^$)SNZIvdTA_Fr1gWRH6#%^^`xftQyO0op~=|zb5go9%A$Ds zqJUW7=u0nthW4p+vu(3u(tU#Rl=iX5BcL3lZ&A3lI;nrW>m0Mr!^2-R%KnbC7iZTQ zML*95^ce?V%<$njypm$*w$onXfeIybJ}E)fMq^g%Bx5>ii;>1}WBkH+uib!HH-_22 zD@edZv2lc!5HG$@-5C|Us7aS%U{T7*Bn4706S4xn<(B$7LX zt0L>qEw|T0bYP)Gh{Qyt#5LG$<(|Se`!r*azR@M_YILV={kb}TXS4_c!Lz6t|0uM8 zD*cor}B-Ilql3yorDy`PZvP*OS!OD^4M!IokwOECqOa$=!_7{5o1BdlsvFS z$<|50`(Nw|cq>rVy%#lQ{}p_PM+3t{=wLldfBaN3*P#K&*XsP4Ci8dCO_TypNI|$4WdfX z`J}>#;zi1UR+fc?8u5=yrFv!_OeB(eRA&Rk+QCPV+CX&3{?TO@J0t8 zo9U03V4#7-8`BV}TbF>r|HPlN&E<+mN#i-%B-k-a5G1|KF;!vSTnw7ULSjD3xoV7+ zDXB0EYz)^Yj+)v+*(!JE+ah1oYcE>CqVL^&MGVL7&`UDaJqVV|BT^soMf!gY`9`Oj zP3#++7!s=8r`{xmzK5A1P7ag~c^;w=FD6v;$Y7RR9q5k3rc6&MpqV>XDarc_}rV zsoIRM+E&u%iM4DQlVec!7jIC68-`2tNg}|chizwmav^F-GUUgo;={qflF2fXa1x={ zaAG?ibOSU>rLZdc5=rG+L>?ieDHwzK!_(&-H?2~J6hEu>za1~Cvo**tlG`v(ohLwuKRq5)1#Sx%Hs)54JttW{xNfmzM2P6E>?|-1TJZxBKE20G>DA?; z6=e?(F{qlsoClnn0d*77ymJ#kP5^9bjcanc)FRq6+oBko9{_uWn~vi-Nk%+#(b{$F zLq8BVeEO`V!NieMVYEH5Sy8nmsqGB+aKmCczOY|ts`|z)v%_fr8Hb+S=~dQz^_v<4 zwSw_1myq{4JJ)zTDzdXFO#b^Us&9Db@%$s^9e2Emsvy~Zk=o#_#+!g5mmF7vVX$7(?&^ONE-MZ_2TF+=^EQ0!&KVVzLw zb?T~;uKaVHELo+Mk-|jEjnTP53+U9{kQN{|6V9Bc@h#2$OY_*qx7gFx(cY{^h+rtJ zK9ybn^op$mgVT6=&C=>`Ofvo(RF*uJ)bY-a974D>|63ehY|3z=W9D*=#8CA|kK5|U zxqr?xWLa(8A5^6Bt;hvq@>{)+a}&?*^2I4(H+I_}Gr=2DBvCdJh=qysi*RJrXKvva zu#TIy3gCxz8VRf^-S1k*BWzmNjz2|)Z>pnHRXOejZdBy=2pvF7o%-h_Uz9e6gbAhP zfL#(HKYe1VQK#5T8j8>RM}jhLa!IOpEPSN~;%zt|>@6PFIN?OjJ1dZ3A=@kEJqMud z^-41SdZoiB`SfL6g?74#+Py1`m2-%!qq|MS=VyDo2K2|St>Td-WPqHH%ntA!a_ z*GvK}mm2BE8qZB*63+C+wHgu6u;K^LzvCh_bVHZ-MjzzhFBrg`T0`;Jgs=xa8uYR~y;SxZNn4#_A7CH`d$mV`>-HL0acpbnf9|R_dTe(eU2W7b>UtxN-|SiY8AtY z_4Iq#S#$|;{bD&}f*;|Xp2inGctJX@!*?Co5H}Chvb`u{>8d95Q`A%AAt*Tg05qhL zRG%DJkOJO{zR~k$G6l48QGxop6<=_cXAWlllTO+$Io6pnI?F?~z3sp|Z^BuP*r*7X zZW9FV4rHqvRdz6*NY>Vv+P(z#BCnET;=E;w>%;q#J$vagM8O=mnS9Eaj)r$T-Jryf z_tmlT8KaW@efd5zP|Dkpam$7{uS~*)TPt<#cGLMA7aSSgZ_$B&Nq;iHgd=W4Nk5{+K} zeqGj1>cQ30+IrR$^h~L2{nw1{``e8E6d(b)>F(ZCVWid`3W%;iMftAh4k-QaH#S8c zm$Z=o^uu#2SWhL%@25ZTND%(gckt!oI`YE0R2`8IqacG!vQ8#4Rq&>fH(fn1j0~ZW zUkxt&iU$+n{T)vhZGj-%WV6D9KK#II7peQ#bOLUe0#yngHVLU}F9u?9t_2)G&&B<0 z+YZ9_It>;>3K(!|Fr^@Ve|5#x%=QQeWo_S96CdT78974(VpXyk!jkc&8hHaL zP;M~*g9Ma|M}fRio|#$vT**s|hQ-z~>^FQ^79Bk>X;m64rNQT+b$hS@ukjn&BwUX@8%K=ESZKs&$ zXX?(o?ey;CBG6AA?``@6s9{{ZtC6j-_4(nG#=oQ#p{K7)K41WWX9htT|Cnrg#mL5x zfcCIr_EEOKf6?o1#pM!c>Dd3icdpiZQ)}T*e z&k~Jek${MSme~NLEDgh1kQM$(kQ$pPZRN>_vQ2ANti8l{405S7Z4={j=HZ2$^7^=` z6HBAR2=l0c(mI8!kcK&`sA$>Hr5_qFSleUKskwGYsNO3m!>}OTm@}(b;Lhe!Si4o) z73fVoljTO%X*;jgN@qj!eH+m7meu$Gs<-!)%MGWkZx!gm>Bs&lIDU{g2U}yy96al| zNKg{@O(g>5d`;|A*_zBzjT@QZVlP+q$afo6qhEtrED6F-Pdr|P=>|j*sY<(G8IgF#L%P`@8 zg7|16rCM(S>>pW`&O>>`KxeL2f8ZoMUfb^!_*<+O$y9RS%1D|->j!VYIEkbzp&6~; z7ASsmUB@F8U+^vLTtdFUMM^i88+7nr*FF1W=<$N>0!s$jYTUTwb#Bn)8*;0@v-xQ(goNI@qoReAV$6Zd&_Idp^@&Gf-PP zXJyGQuocBW982gsyMy25jnOd}qw}@MXz&v^(QbtU$QlQMlC4#6l7z?IL*{ON-);ts zHqt0AfO-XWrN2S-#CD*iR9AjTcU#JQEP$*TNVK^rQdN5J1Rr=E#SWHuU!+CveL>9x zOJE;3^BwKQZv?p{*JTx?^IX|+iTPbJ@q zTy1DQBD&L%)i+@h2Ez^;MTYjI)}&9j&6S6UO6GkNq&(ZW1dYns~DY=PsH5KFS9i8Anu>FCX`CPwii`2;xG+2|3Su6!ZT3{^; z=B6#K6-4try?1^J(E)jBw=+C=X7FQic*R*X|d({H;bDIQ}RPw zURH9Dzi7{kNK*l=uGs%ts&tI5$9K(ca|5)$!3YL3+oHYw2=nwbB4d1M& zjk>q#Ew#6sW3U3>=ruwV>hVvhfpS0mJs=l!zP#dOGqv`V?dJ&utJ=Be#Spy3C&4Ia zlOU6J|Jm=d098A-F6MXpBjo8D97%~cgnw=q03?&&k%M=r30;ZYNm&Cd6y_@`2_#$| zKVn;XXN4n+pf^)ZawO02H0`o1BE!324}s@&j66n96IiEJ&O1M-0pv!%EV%8o{LVdq z4Ua%7e5XLg0iX?UNdIU6C>}S|D6CNQ+(EkIFHBU#c+BB2Si{2}bXmBUQG1?QWfN2I zhPR|aqxA7rQ}bpBJ21Xn>easmw?yH(ZslolKfn69vb~pwkP4?id1^Lmtrsg{Pjn^sRH10ie@eSR=rMU8o~kxnxs0Zns`)ntZ@RdUKU6(WPk{qz)Y1ZWh z{K~H+sk=g}rqYh#Ifl5($7jN)y!PwgPJ*v5dXoI}xvHGvNKB#6^I+z4 zI?!mC7AQz@VgVfr7|3rg0fEVgE0%PCTxVK||mAY3`$Z=qo4f?fD3(8bt z;VhV>95KT{m0(l;Du&aMMENMV;O4tQke7c%!Y`xHJ9K8=(NFGmA0*(XcZyzn1-#12 zKxKy96}#JgJe;_=&1dM0^Fp(}Tq%Z?$ z1z5l50@e;dor_+}uf1=`F5yU@b$Vp5m^x%GNV$vW+7)6bhX|m=86Jxkt4iT=oB6Qe| z?n-n>X&n}bwI=fi_$5E2;HPdLn$jsoO4Ie{jc3=}I*LWs58BMYBCp<^+h)6<>8LLX zhIWUpR_4R z+lqf`%SqRh%vUnw(=|}qkZ(?A+8*;k<$GPH$rd^;rZx775Kg>%TT3WXC7SfOO(&@< z0(j4*!qW^Fg0f?n|EH~Y3eL3+mvv*?wr$(CZQIU>?Gf9yZQHgzVkbNQntRo(UA51@ zqwnCUxBKqy3+F=cu6BA=(bu^LJcSp0hdU?xNqoo=2kt}8YEc7&dW7Cva{u^C9{Jt; z+H>;M!XU0Z+m7zi`X0UcZ-4CK?{)dsFOu^yUX2!YwM18=9$hXEwBN9?qGTpmOUh<4 z$E19&hui6(NqQmW_z!)`)i^(M?$7vp_O>>mQz9vs6c0D4=Onna--BBQRDL~?y&}>U zCfT;hi%E>S%@$$j^gL6$O85q{&(5ZNO7gsBYjMybeMx~zxR51{V9FWq7FSqU8Bj$7 zscO->Fwc8gx=uji5IrD3UDosluGv3V`MEshHtKQU3mgym3Be#!*>72x%femuGXUWR z*E5BmG~w3Wp>Weq?NTh0z;Q)R)WIQqoDc*MS@~=dgWW&R(OL@>oy0ZSp(ACVHa!I^ zOay_~YFO9n%ZW*r*bA(9E5R=l^mcNx_Ivw`wzavn>g%hZB@ln#y-xF>6ksNR*~%vJ zyviSKUfV9O3UC|tFJTE*JC!8=MOPh_yRNJ8TzGLt)w0(Z{4JS9P^;qhuxpOo2nZ={ zkSJM{-nxNw7Jx0nK8}X773EJptskc0suIBN&YQS5oX3PAGyJ0``&32EZ6K}` z%OI~;0iQ1VB$T{uBXwwrAP#HQY^aoEjeo~Leb9(1s5DZ63W z17L41CQcfH_$fd4jVZpf;=Ii9 zTV){j7lB#4yylPkeQspsw-^w*Pt~UYBTRdp@k$${QXR5pi}iTplq?+keQg-X+ggN5 z#&5M;}p%R3o0JBlB z)Vb_tYAmldU*8cjQC^(cCDW*Fo(KRHJ+8GpsXn-dF=uYGn7dV|Lp2LWP^;xu@vc8> z4?>seh)yEeKHTIr1hfz$Ol~D(0c1H-{b%+s>YD;>$7v#e6e^n4xgyKvBtK%>o&&1- z!%n?ZoEz5LaRM3bF6|3A=joF>>jEH3^xW3i~!K9Wb*efKQ%S^^6ZmEP0TYCRLh3@j-4&V!;ubORkl{Hwh;X zy;plUjn`mDd`E4T@xp4R**Pa|e}<{Dd1osFteL7N-c)BmvaW{{>5N6LdVBSQzY7Vd z9{gk1RjP$Q(!J>i;G$Mfz0`R&zN&f|{B)o4%#Rs?TsXW(nCxM`<#8764LXIMPL`bL zuT8G{Y7ymU?az2QythJ=7u+{gvtgn~mbVkf>FF8%vdVPiuUP5%pe(6VASZ$4E!}jp z%tP_Ag63r*rFiDRj29Qf;tp=O)gz&fJsi@0t_yo7vWYwSz&_s&v(hJ(j7?WVQzlPg zS|6(ya{qB_kU>%-?T|8spe7!he#cfDNx<;d;XkTCYMLaW3{MV~p+4PnuC|GNla~%e zE30Bj89T`Px(@-h&1aJNCIJA5I%lb32*LLTF%p9gb8EyX!|jRqkvGN1HZ|mxGUpw# zY^5F0(zNHuz@3xl$0sbW!m-e^uZ>x_L-ceL+smdCXK>? zXRw;#h9q|-;^;=(t)!Mbi{OvMW#K>s`{Or*6skibWs8X={8s$xRY@6Ca3V9tV8fPf zNZv9-f}76{Y+B9r+((wL%1smydNn@1EAD?Y3kV0*0oGG;+C%TzH9;_UXZ%}=!fput zs&YeS0H(@gnweLN3+WF%)lhTFYONW_#?Q^r_XC4>Hn^HN{C;cPR6S4p|t0cZ52P;O2Zi*sZW;Vf$pB472{2nx@ITa1?jHL9uVYh24>`q{Fe3!+MKNaAL?m% z_kohGED$YO)tk;VlLB615s#~hR{_M%Nx1f8Ex|WQ3AX3sXze_Aj-gCMuWIGFd-U}4 zAj^0B)DwjiL)x&;dR<}4y=!VdmhUB0u}CD84V`g7gSoMEf!aXKlN&(FdZVw z*I+&*lXjyuL7UUrNk%O^E%7QWf(7$|&KPJz&TYM)cb6ydboTq_mLRGRAIoM^`Pd5t z-fv?Tf8(7vNB*LaZA*F9w&pML_6Ms-L|rNfdk8Mbo9uDE`{k)V89(G~COXs&t6+5Y zw;HVRQIUNM?YA3f5KM_iJS`s#dosHRc`85kh9KhL!>^hO>$8EjC~}KM+^D?~nC;3M zE1jU5?zC`UNyL9CLeaJ^}zq?zGp#uR<(g$Jdm!YBKr8_zl2Kn^n~X*ux5vfa(WDuR&bo$31$9;RM`*p-|LX(V!>w z{G?&qk6;bKmVs1yN*JaH@j;LR04>s=C1&cFapRz__o$#K)P2y)&OW572yH+Q?&-Rg zclujphjf@{CWdX#c*Rn6FT;pB{7kjs-6{8+S2Z~@?&1k|^Ec-3;p>6K4*@YDz{~NJ zegpvy?<-5!_AT$BoV7+_)q?crI9~+bJT#epBCA=p)^O0uZ?YtJmRo+ve~b&c&GfkF zdJ{Imnm~VG#C@-tpD~i>ueEwXSy)Ywy&nKwT8)|c^tj#ySdFTfUXq$~HO^xCjXN`x zk~dmN{H$D8Kn%32dM{-zYaQK^m?V5d`Z7H_~eC-I&eXddVO=5b`i z^b=!iG86uyF%BXYNtBf+Y+UbtCoR;yjyCNbQDZa{pD0vFQO#XzG9}J$PMoY@jD;x+ zNZa70wx+)oV8$)ur>{nl3KL~w%5e+9FBwT+x4m595B`GaAoH>gWf;7iqEk!8q)ase z0<_`9eISqga~F-5AY7`uX@+DG$UKu)yb9UnMoG)IHD`11@}WYn{a}u~RDcv4Hn9qg z`9zrj0x4l-@yroSq2S#+X9oL zs$PZ?b$HOaYgoN~J;6AiC>~$sUFs~HclP=K6WFy0je{A=PU`avo6G0W@#p|j7lxs1 z58)pT1ePz0=tw_eAO11;tG^(iq7;VVdZx`4WP%vzk1olt=6dDc+`5K}mslQzuz{)q zERqJt5O>6>1G_xTr)j4$T$u(_dqhFJbcQS|-=OXM$^_glC4HLWBA;_l{Z)kOi}BrX zdm8-xGpZ}&w#*;)u+jz4tSbL&UZV7VDDy~JUd2~kWi;uIdaavM(^jDiD_ot55l9Pp zxuJ{oe#Da&$0CPaG3diAc+2?m?pBI?K>9wTOCN9@c$h;Hkj@Wt(>hGF-=s_VFC;>SQ#)r zjD;)=Sk*F!Nu&ubzD=A{sp>nD2Lf>5&33a-8(bNT z4qC#1tUfT+=)Bq4__rckiKWR=Ri}k2rNtyGXt|Q?0e}m!`;e0rjKcV*;tv}}{q!I8 zKFBuE6(+hht>}TNu*jXG!k#1H;hSejHtT-qx}k7%e3mGM z`E$;_qSl0^v`6dWq4{7K)GcHS0^=q|wAz2{ttN}()LiJ~m^!++>raL`(M;Y#YCuVU z8ji&z-3{|hLpv#kJ>E3IuUMF?z{-zstYtUbKS0LR+g#9pt`Oc!3g0%yjpCCn{ongZ zv1aAccI9RI4xrJZCd%H?kzq^PQ<RO`Ik&v=rVkJUzWN%rz?r+xBJF+W zLq6+7Mf%60uC=k*+-hyNhTR94=%9rH?f37Oh^#8~Sjb^@ zAVKGH@rha}0J%>+8goxQ(&EGBd@7S<-_n-J^sL6aGuwIHb&c&ut&B_R)|Obfh~DP( z>I&ZsVZ-4nug7i5!F4GydwTik2*!T}{V!U&A@JLW{*_#mWPxJ5$O~%I#p){MVF;pw zUN~w@jVo7cD5`I_$?mBFFm@Rg>p|d*__uG}cY6L6tF9@iCR8XB-I1*Cd7T-bpNAeP zrqS58F_BEsvnFaUcO_e>&f!MD~xlQKY?YgKSq^7u-A`p@m60mlUSrugJ7x*-}-wu+Eq zh=wV!&IkhsxtugY57anB1D$2RU zpHxexh-obZ{R0LYI}nq43De~;tWb741|xc}z4&v=PPJOO;qhz(b6nb8K6hrSn!u)m zV%5eT9t^SFQpPO!du#!KQ;umEt1aBby4w8Rl33I5(#zBmnJoMqJJb+pv->xEW;t@7 zkTRXBB47tAk%tDeezar;(q-8%ZmO{9L$Rez<*w{8&_IPZmp7=zOZ(AURajUG-p4vm z;>|$>Z;U&Byo-B1MJ>TR5kmac>bYSxcdun7+!1}y;7U`-azGs6*(R_HdI9#)gj8H2 zZXGuiLZeO2?9emOOYhSGn8{%qcyPrTeQ zZO_e`k$LMeFM7#m;OmmO=Rf+5%?DlZp2o5f>nSsmHI}lk>?&ENM}C?-QgTi09~h-{ zM+i)$YP_9xdrbHN|9^FTv-Z+uXfeTFU%5rX|DUcW{UGF6LYIDi$ow6~@cEwRZKt53 zSUsaOyQ4}?w!yhOuuc?9=#N}8lyfi+kx8Qce|0_8|JC*WPP^IvN7sv&!nYK#L`s<| z2-hTF;*GH%)m9sxw+pDsDT|6>yccp4A4yM9{`Uc%B};Y7m zsH~j+JNe+Gw7ilxaEz*`zuU^qOeL8^O7nP-jYhSWnEojeIc-tWywv$lmxy=>ELL9% zfjo;vX5jxS`yi&q`u5ZHekT*-UAi6e|Euin|5NtcFyzQ>HgmU%b;$pt?3aA_ZreMF z&Y9#AAOF}(4*<;pq>Fi43*id{>4DmV^`6BU?ccvbajFc(gYo1tl~e}*Q__2IiU zdpE7tsh9E?BHB0l4>acZ$0_3)@X>cy*L01-@)y)l@@evJ(c#;tE?#R4X6XKvf`0SR znSL#TtWBa-E%Zn~Ks_y+hhKZdeKf@>7kWu&sKBu^tk)2T$apzuA<1sZTXX?md>Kdd zy8BSIX#}4d9oq!r64RY-bDKeC-qJQ}UdiNM%u^d?ZLm>yM5(%s1L2A>wuW#1fv*h~ zxSB_F$WGkuJpe{#`14-bx_r*VKyGdQG@sxB*ReZ2^Tz01u zT!Gtg8Ojo(|IDBMWQiTAHap*&&nANuw;&%|`i1lb+^kS9Hz)wJeM`;bVo{h9$w?q^ zF)%Tp1j&gGF4wU}o{Nk^_>D^{3u0p27iIeK_Qt!rRor@{Bg<`S4 zufl%HTAnzphUkW{1%c#DexwVlJ?-QRB&xQ-K5+AR=paR!z9I93PQb~$yNy>gZZ&3d zm2#*Q$xQd*UuE^-z5Mev?cXAe;>+`C_m|Uw5Raer@smhG^!N06F7HxOjy{=N@rR54 zvdZ><$JYOX5kU&v`IhU>7k0&vh4Mjmb*TahN6s|yq=eboq18Vh|J0K>92b%Y*m@y+ zN;`K)v+elSmzdBB6}Ak|#w7)zH#8pSNB4snrGpxh%@_PRB*D+&JNBeT-0hOh%PgNs<`P-F1~iENi)p{$WngVq z5HMHPbsZRfycBbMjQVRU53EbROL^MI>Lf;sCTeVC#UtToHp=BX} zxrRY2*u+#*sqV``~l=C{$!U<~Lcszp_Gy!$>2!yFm z=0gt=9?JQhk?j5kx1f}uehHj$@7LwVQf3dzqJs@CIW-D4|IeZ96|W>{F; zk>}sBZd~Yt<-+Tv5#>)7iN!-IMAjvOS_$Y%n7eRJC^(S6DG^G*GmjTN%`HI!y&{OZLh0yST`J`o`uh7&e>H{T!hM*$27-KYT(dhA zFQqq!dG2yA6w2z}r_L-}MyN@UlO{q23j!!s&N`5*`Jr}MUB3f__Fs(`5JcxArNPit zsJ>!kI2!pD>d8fO%{c_N%Cw(z~ zIy|2q=Cce3^8$9vH@byxzQ6gho5lWe{8Xx1ZHFrwT8~D-&R(%!|1Vby_kg83*P4n~Y?Sj1F`fRbEr^T#(&M2SgqT`tp&mZ5vCKApt$p(Oe3_QI|83B9kpl;)Rye0tty3AD9NUx3ww3U zK=z$SoKeUDUp$MQ2iR{(8ige@^3p0oCVQ(ygK9$a;c>g*v$H3=`&9a21oH&6#Ot4` zU=a@njMIb!V0ll_$_+j$EfY$RJTSl?^x2%{-hVJN=nzP#|8SV5BvmO0VxsQb6Fe1v zQUYtjg(O_#9enOjA9LAHAI|R{x8KIsz0>-_#BczFSVj&v@)MBnpe(N6j{6OQQVV9H zL5A}aEqt2-duzl0tj~CUIbg;(&SI>ejmc*B*SZc(`sWglNSuwS5Nr`BaBIdIJ=b#3 zA9!th-yx`rD14!?lmk#g`2m9_Q(+aC5V?Kr-!oT6;yE{mva3|gZBYkk1p^FXBfwF# zSy7=;Bt_P-fV7bW(%dFnr><_hf>=6H-nm$m8Ln1_>@GHd9jnp8sp4Y=R3O2qWtq+U z9HCZn0-rp1aYa$7LLvrkafUwz_uvFzHZ||StkkzK_JKGlN#OJ+AiBr)gSe0kZoHGT zf4nROPJ9insBrJ|B&;}aqw*xw;BR+44|VXlUMfcUyj@fTV^^T{wGqn_XohMkRFM%U zNI`RY(HbwS3M$7(Qe?DvN?X6sm1%tr%a%_J{jQHLAxhBPo4hjgSAc>^568v6x7F`r zzvnpH05R*bi8wN<`{NZHG5Ns0Y`c0ZnuP1j{o>AYTCGzv^01$e>M(NWzTEj9#B;M4u*fy#EMO z!84Y_G#j5=t*GUW6a~Y5vsURDp$~fUHJk zy_6lCt1H#N8aKGDxGW8tpk!_@>ufEm+?tI6yPmIdvyE+4p-N|Q;xikG%1DIgYI`lt zKHGSH7q3)mw9?G86DqE(AEM`4Y_{D;g(MN9ij~d~VSyIN5-%yg2_hv>g{CzQv6YPe zb2-0}?i*xS6Q(1UJ7{UwyxiD;2}Tv*2--j;O^0Ge{Pp*IL}~#jo$5xwuc+8fn!4B! z*kzaQsBzj6IjB~S7Nj?U;c^p_hheZoi9~aSBLic!;5c=vEQ=kGgVW<41gisLVm)$+ zDekIT8hf@Mn7r&5D>fte13(g}Cv99wq2&HwL{Co6R!SPxln!gXo9`hfPfCK3Q&7QLy9vv{NO1p zZ|=l&#Bb8EsZBUeCTpYDh_Nb17RFdxa%SSzh^gpsQf7Kv9&q?9{miK@13w@p=2v?n z=FH)06En~_n{zt?X5vk8vuik3F|%0Do}4KjwXKhN6SH$uayIMXA85WH=x%Y>FopN= z3^Ahp$V$;oL8KH;Fh349mJ>fBE*88_-77aCm(4ENXbV#s()aRHPXXAUqyqXE+ae3) zx(HNnw<|;~mqhUdULS#IwRi4TPq|%p^9}sz`er#T1y}H~re|<|LF+gy8vt#2$e^c@ zAoYlg&T5vgT}-3VL+#n#-fYh50F%|E>yHA=^4brB3*5D5Kk9W*gi#f2Le0cDL)i7~ zuOhdy2ho)%{;cV3b(`59Fh%!)mD5l>gMQAV482?J2HO)cg!KZk>!vq97Dmin3x{iI z_fZ|64uMAlxl6`M@l4!Fx!5cnS0Btz=;4HZDPYFm1F2^ur$d+f>ZYLCF<30q{%c6y^MFF=9((7x*GSLfQQpluC1~hM z$@prP4UbWeFlcAy9lXJYl!sKE&OZZQ)zyVZ&im(e!zmH|a37H#r(6FQONEy=9-IMl zAFQfd_~VY&CYD5=x7hl@w~@l+AA85z^vSG4pxr5M*Mxq%f&Z$&o>2H4f31M7xAFu2 zNM;FyOo}KTg9;Oe6iLA#Wq7=lTL#R>=pAt6`hU3Z?o1Y-7)dJ?7LGM4Cpz<`9)Z_x zi$DZ{-;+uOMwl{{+KjI#KIgNL3Oycb2*3!Nrez7z65K>(GnENiJ>sE_g`n>O0TA>p zqYqXn^br092dSOXL)Koj4yWe$>n3gNO% UJ8jM22%WkIA9gZ0q#pkRq2I59uaO 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 Date: Sun, 21 Dec 2025 18:33:50 +0000 Subject: [PATCH 007/204] Mentions: Fixed some users not showing in mention selector --- resources/js/wysiwyg/ui/decorators/MentionDecorator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/wysiwyg/ui/decorators/MentionDecorator.ts b/resources/js/wysiwyg/ui/decorators/MentionDecorator.ts index 84d66488589..d6cafba43cd 100644 --- a/resources/js/wysiwyg/ui/decorators/MentionDecorator.ts +++ b/resources/js/wysiwyg/ui/decorators/MentionDecorator.ts @@ -74,7 +74,7 @@ function handleUserListLoading(selectList: HTMLElement) { } const doc = htmlToDom(responseHtml); - const toInsert = doc.body.children; + const toInsert = [...doc.body.children]; for (const listEl of toInsert) { const adopted = window.document.adoptNode(listEl) as HTMLElement; selectList.appendChild(adopted); From 8fc9a2af4ea79ddf1d8b7558708fac1f3af6d1d2 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 23 Dec 2025 18:33:54 +0000 Subject: [PATCH 008/204] Lexical API: Updated docs to reflect public event usage --- dev/docs/wysiwyg-js-api.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dev/docs/wysiwyg-js-api.md b/dev/docs/wysiwyg-js-api.md index e899aa0c8c3..4b4fafe5624 100644 --- a/dev/docs/wysiwyg-js-api.md +++ b/dev/docs/wysiwyg-js-api.md @@ -26,6 +26,15 @@ via its properties: Each of these modules, and the relevant types used within, are documented in detail below. +The API object itself is provided via the [editor-wysiwyg::post-init](./javascript-public-events.md#editor-wysiwygpost-init) +JavaScript public event, so you can access it like so: + +```javascript +window.addEventListener('editor-wysiwyg::post-init', event => { + const {api} = event.detail; +}); +``` + --- ## UI Module From 3336e0c6ae961e7d1b0b3e09b2b550c63886afcc Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 24 Dec 2025 11:48:42 +0000 Subject: [PATCH 009/204] Deps: Updated PHP packages via composer --- composer.lock | 66 +++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/composer.lock b/composer.lock index cd4ba68c56d..93bf172c6ec 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.368.2", + "version": "3.369.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "96397db9a3fd0b5e6b3c52e363b6a55831a93b1d" + "reference": "5e3f541e344d71f3b9591fe1d94d9576530fa795" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/96397db9a3fd0b5e6b3c52e363b6a55831a93b1d", - "reference": "96397db9a3fd0b5e6b3c52e363b6a55831a93b1d", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5e3f541e344d71f3b9591fe1d94d9576530fa795", + "reference": "5e3f541e344d71f3b9591fe1d94d9576530fa795", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.368.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.369.2" }, - "time": "2025-12-18T19:07:30+00:00" + "time": "2025-12-23T19:21:43+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1596,16 +1596,16 @@ }, { "name": "intervention/image", - "version": "3.11.5", + "version": "3.11.6", "source": { "type": "git", "url": "https://github.com/Intervention/image.git", - "reference": "76e96d3809d53dd8d597005634a733d4b2f6c2c3" + "reference": "5f6d27d9fd56312c47f347929e7ac15345c605a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/76e96d3809d53dd8d597005634a733d4b2f6c2c3", - "reference": "76e96d3809d53dd8d597005634a733d4b2f6c2c3", + "url": "https://api.github.com/repos/Intervention/image/zipball/5f6d27d9fd56312c47f347929e7ac15345c605a1", + "reference": "5f6d27d9fd56312c47f347929e7ac15345c605a1", "shasum": "" }, "require": { @@ -1652,7 +1652,7 @@ ], "support": { "issues": "https://github.com/Intervention/image/issues", - "source": "https://github.com/Intervention/image/tree/3.11.5" + "source": "https://github.com/Intervention/image/tree/3.11.6" }, "funding": [ { @@ -1668,7 +1668,7 @@ "type": "ko_fi" } ], - "time": "2025-11-29T11:18:34+00:00" + "time": "2025-12-17T13:38:29+00:00" }, { "name": "knplabs/knp-snappy", @@ -1739,16 +1739,16 @@ }, { "name": "laravel/framework", - "version": "v12.43.1", + "version": "v12.44.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "195b893593a9298edee177c0844132ebaa02102f" + "reference": "592bbf1c036042958332eb98e3e8131b29102f33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/195b893593a9298edee177c0844132ebaa02102f", - "reference": "195b893593a9298edee177c0844132ebaa02102f", + "url": "https://api.github.com/repos/laravel/framework/zipball/592bbf1c036042958332eb98e3e8131b29102f33", + "reference": "592bbf1c036042958332eb98e3e8131b29102f33", "shasum": "" }, "require": { @@ -1957,7 +1957,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-12-16T18:53:08+00:00" + "time": "2025-12-23T15:29:43+00:00" }, { "name": "laravel/prompts", @@ -3469,16 +3469,16 @@ }, { "name": "nette/utils", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "fa1f0b8261ed150447979eb22e373b7b7ad5a8e0" + "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/fa1f0b8261ed150447979eb22e373b7b7ad5a8e0", - "reference": "fa1f0b8261ed150447979eb22e373b7b7ad5a8e0", + "url": "https://api.github.com/repos/nette/utils/zipball/c99059c0315591f1a0db7ad6002000288ab8dc72", + "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72", "shasum": "" }, "require": { @@ -3552,9 +3552,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.0" + "source": "https://github.com/nette/utils/tree/v4.1.1" }, - "time": "2025-12-01T17:49:23+00:00" + "time": "2025-12-22T12:14:32+00:00" }, { "name": "nikic/php-parser", @@ -8942,35 +8942,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.11", + "version": "11.0.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4" + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", - "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.4.0", + "nikic/php-parser": "^5.7.0", "php": ">=8.2", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-text-template": "^4.0.1", "sebastian/code-unit-reverse-lookup": "^4.0.1", "sebastian/complexity": "^4.0.1", - "sebastian/environment": "^7.2.0", + "sebastian/environment": "^7.2.1", "sebastian/lines-of-code": "^3.0.1", "sebastian/version": "^5.0.2", - "theseer/tokenizer": "^1.2.3" + "theseer/tokenizer": "^1.3.1" }, "require-dev": { - "phpunit/phpunit": "^11.5.2" + "phpunit/phpunit": "^11.5.46" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -9008,7 +9008,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" }, "funding": [ { @@ -9028,7 +9028,7 @@ "type": "tidelift" } ], - "time": "2025-08-27T14:37:49+00:00" + "time": "2025-12-24T07:01:01+00:00" }, { "name": "phpunit/php-file-iterator", From d93354ff0ef5c384ed9db2b57df0dbc213ce3e7c Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 24 Dec 2025 11:51:37 +0000 Subject: [PATCH 010/204] Updated licenses and translation attribution pre v25.12 --- .github/translators.txt | 7 ++++ dev/licensing/js-library-licenses.txt | 47 +++++++++++++-------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/.github/translators.txt b/.github/translators.txt index 67eea48743a..61a6697fcf6 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -512,3 +512,10 @@ David Olsen (dawin) :: Danish ltnzr :: French Frank Holler (holler.frank) :: German; German Informal Korab Arifi (korabidev) :: Albanian +Petr Husák (petrhusak) :: Czech +Bernardo Maia (bernardo.bmaia2) :: Portuguese, Brazilian +Amr (amr3k) :: Arabic +Tahsin Ahmed (tahsinahmed2012) :: Bengali +bojan_che :: Serbian (Cyrillic) +setiawan setiawan (culture.setiawan) :: Indonesian +Donald Mac Kenzie (kiuman) :: Norwegian Bokmal diff --git a/dev/licensing/js-library-licenses.txt b/dev/licensing/js-library-licenses.txt index 95acfa6af78..d7ad4ecc8f2 100644 --- a/dev/licensing/js-library-licenses.txt +++ b/dev/licensing/js-library-licenses.txt @@ -1619,20 +1619,6 @@ Copyright: Copyright (c) 2015 Vitaly Puzrin. Source: markdown-it/linkify-it Link: markdown-it/linkify-it ----------- -livereload-js -License: MIT -License File: node_modules/livereload-js/LICENSE -Copyright: Copyright (c) 2010-2012 Andrey Tarantsov -Source: git://github.com/livereload/livereload-js.git -Link: https://github.com/livereload/livereload-js ------------ -livereload -License: MIT -License File: node_modules/livereload/LICENSE -Copyright: Copyright (c) 2010 Joshua Peek -Source: http://github.com/napcs/node-livereload.git -Link: http://github.com/napcs/node-livereload.git ------------ load-json-file License: MIT License File: node_modules/load-json-file/license @@ -1928,14 +1914,6 @@ Copyright: Copyright (c) George Zahariev Source: git://github.com/gkz/optionator.git Link: https://github.com/gkz/optionator ----------- -opts -License: BSD-2-Clause -License File: node_modules/opts/LICENSE.txt -Copyright: Copyright (c) 2010, Joey Mazzarelli -All rights reserved. -Source: github:khtdr/opts -Link: http://khtdr.com/opts ------------ own-keys License: MIT License File: node_modules/own-keys/LICENSE @@ -2318,9 +2296,9 @@ Link: https://github.com/ljharb/side-channel#readme signal-exit License: ISC License File: node_modules/signal-exit/LICENSE.txt -Copyright: Copyright (c) 2015, Contributors +Copyright: Copyright (c) 2015-2023 Benjamin Coe, Isaac Z. Schlueter, and Contributors Source: https://github.com/tapjs/signal-exit.git -Link: https://github.com/tapjs/signal-exit +Link: https://github.com/tapjs/signal-exit.git ----------- slash License: MIT @@ -3343,6 +3321,20 @@ Copyright: Copyright 2022 Romain Menke, Antonio Laguna <*******@******.**> Source: git+https://github.com/csstools/postcss-plugins.git Link: https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer#readme ----------- +@emnapi/core +License: MIT +License File: node_modules/@emnapi/core/LICENSE +Copyright: Copyright (c) 2021-present Toyobayashi +Source: git+https://github.com/toyobayashi/emnapi.git +Link: https://github.com/toyobayashi/emnapi#readme +----------- +@emnapi/runtime +License: MIT +License File: node_modules/@emnapi/runtime/LICENSE +Copyright: Copyright (c) 2021-present Toyobayashi +Source: git+https://github.com/toyobayashi/emnapi.git +Link: https://github.com/toyobayashi/emnapi#readme +----------- @esbuild/linux-x64 License: MIT Source: git+https://github.com/evanw/esbuild.git @@ -3396,7 +3388,7 @@ Link: https://eslint.org License: Apache-2.0 License File: node_modules/@eslint/object-schema/LICENSE Source: git+https://github.com/eslint/rewrite.git -Link: https://github.com/eslint/rewrite#readme +Link: https://github.com/eslint/rewrite/tree/main/packages/object-schema#readme ----------- @eslint/plugin-kit License: Apache-2.0 @@ -3792,6 +3784,11 @@ Copyright: Copyright (c) Microsoft Corporation. Source: https://github.com/tsconfig/bases.git Link: https://github.com/tsconfig/bases.git ----------- +@tybys/wasm-util +License: MIT +Source: https://github.com/toyobayashi/wasm-util.git +Link: https://github.com/toyobayashi/wasm-util.git +----------- @types/babel__core License: MIT License File: node_modules/@types/babel__core/LICENSE From 38d3697246a294d011461ceb9850bc45bcef61ce Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 24 Dec 2025 11:52:56 +0000 Subject: [PATCH 011/204] Updated translations with latest Crowdin changes (#5933) --- lang/ar/notifications.php | 2 ++ lang/ar/preferences.php | 1 + lang/ar/settings.php | 6 +++++- lang/bg/notifications.php | 2 ++ lang/bg/preferences.php | 1 + lang/bg/settings.php | 8 ++++++-- lang/bn/activities.php | 2 +- lang/bn/auth.php | 10 +++++----- lang/bn/notifications.php | 2 ++ lang/bn/preferences.php | 1 + lang/bn/settings.php | 8 ++++++-- lang/bs/notifications.php | 2 ++ lang/bs/preferences.php | 1 + lang/bs/settings.php | 8 ++++++-- lang/ca/notifications.php | 2 ++ lang/ca/preferences.php | 1 + lang/ca/settings.php | 8 ++++++-- lang/cs/notifications.php | 2 ++ lang/cs/preferences.php | 1 + lang/cs/settings.php | 8 ++++++-- lang/cy/notifications.php | 2 ++ lang/cy/preferences.php | 1 + lang/cy/settings.php | 8 ++++++-- lang/da/notifications.php | 2 ++ lang/da/preferences.php | 1 + lang/da/settings.php | 8 ++++++-- lang/de/notifications.php | 2 ++ lang/de/preferences.php | 1 + lang/de/settings.php | 8 ++++++-- lang/de_informal/notifications.php | 2 ++ lang/de_informal/preferences.php | 1 + lang/de_informal/settings.php | 8 ++++++-- lang/el/notifications.php | 2 ++ lang/el/preferences.php | 1 + lang/el/settings.php | 8 ++++++-- lang/es/notifications.php | 2 ++ lang/es/preferences.php | 1 + lang/es/settings.php | 8 ++++++-- lang/es_AR/notifications.php | 2 ++ lang/es_AR/preferences.php | 1 + lang/es_AR/settings.php | 8 ++++++-- lang/et/notifications.php | 2 ++ lang/et/preferences.php | 1 + lang/et/settings.php | 8 ++++++-- lang/eu/notifications.php | 2 ++ lang/eu/preferences.php | 1 + lang/eu/settings.php | 8 ++++++-- lang/fa/notifications.php | 2 ++ lang/fa/preferences.php | 1 + lang/fa/settings.php | 8 ++++++-- lang/fi/notifications.php | 2 ++ lang/fi/preferences.php | 1 + lang/fi/settings.php | 8 ++++++-- lang/fr/notifications.php | 2 ++ lang/fr/preferences.php | 1 + lang/fr/settings.php | 8 ++++++-- lang/he/notifications.php | 2 ++ lang/he/preferences.php | 1 + lang/he/settings.php | 8 ++++++-- lang/hr/notifications.php | 2 ++ lang/hr/preferences.php | 1 + lang/hr/settings.php | 8 ++++++-- lang/hu/notifications.php | 2 ++ lang/hu/preferences.php | 1 + lang/hu/settings.php | 8 ++++++-- lang/id/notifications.php | 2 ++ lang/id/passwords.php | 2 +- lang/id/preferences.php | 1 + lang/id/settings.php | 8 ++++++-- lang/is/notifications.php | 2 ++ lang/is/preferences.php | 1 + lang/is/settings.php | 8 ++++++-- lang/it/notifications.php | 2 ++ lang/it/preferences.php | 1 + lang/it/settings.php | 8 ++++++-- lang/ja/notifications.php | 2 ++ lang/ja/preferences.php | 1 + lang/ja/settings.php | 8 ++++++-- lang/ka/notifications.php | 2 ++ lang/ka/preferences.php | 1 + lang/ka/settings.php | 8 ++++++-- lang/ko/notifications.php | 2 ++ lang/ko/preferences.php | 1 + lang/ko/settings.php | 8 ++++++-- lang/ku/notifications.php | 2 ++ lang/ku/preferences.php | 1 + lang/ku/settings.php | 8 ++++++-- lang/lt/notifications.php | 2 ++ lang/lt/preferences.php | 1 + lang/lt/settings.php | 8 ++++++-- lang/lv/notifications.php | 2 ++ lang/lv/preferences.php | 1 + lang/lv/settings.php | 8 ++++++-- lang/nb/editor.php | 2 +- lang/nb/notifications.php | 2 ++ lang/nb/preferences.php | 1 + lang/nb/settings.php | 8 ++++++-- lang/ne/notifications.php | 2 ++ lang/ne/preferences.php | 1 + lang/ne/settings.php | 8 ++++++-- lang/nl/notifications.php | 2 ++ lang/nl/preferences.php | 1 + lang/nl/settings.php | 6 +++++- lang/nn/notifications.php | 2 ++ lang/nn/preferences.php | 1 + lang/nn/settings.php | 8 ++++++-- lang/pl/notifications.php | 2 ++ lang/pl/preferences.php | 1 + lang/pl/settings.php | 8 ++++++-- lang/pt/notifications.php | 2 ++ lang/pt/preferences.php | 1 + lang/pt/settings.php | 8 ++++++-- lang/pt_BR/notifications.php | 2 ++ lang/pt_BR/preferences.php | 1 + lang/pt_BR/settings.php | 16 ++++++++++------ lang/ro/notifications.php | 2 ++ lang/ro/preferences.php | 1 + lang/ro/settings.php | 8 ++++++-- lang/ru/notifications.php | 2 ++ lang/ru/preferences.php | 1 + lang/ru/settings.php | 8 ++++++-- lang/sk/notifications.php | 2 ++ lang/sk/preferences.php | 1 + lang/sk/settings.php | 8 ++++++-- lang/sl/notifications.php | 2 ++ lang/sl/preferences.php | 1 + lang/sl/settings.php | 8 ++++++-- lang/sq/notifications.php | 2 ++ lang/sq/preferences.php | 1 + lang/sq/settings.php | 8 ++++++-- lang/sr/activities.php | 4 ++-- lang/sr/common.php | 2 +- lang/sr/editor.php | 2 +- lang/sr/entities.php | 4 ++-- lang/sr/notifications.php | 2 ++ lang/sr/preferences.php | 1 + lang/sr/settings.php | 8 ++++++-- lang/sr/validation.php | 2 +- lang/sv/notifications.php | 2 ++ lang/sv/preferences.php | 1 + lang/sv/settings.php | 8 ++++++-- lang/tk/notifications.php | 2 ++ lang/tk/preferences.php | 1 + lang/tk/settings.php | 8 ++++++-- lang/tr/notifications.php | 2 ++ lang/tr/preferences.php | 1 + lang/tr/settings.php | 8 ++++++-- lang/uk/notifications.php | 2 ++ lang/uk/preferences.php | 1 + lang/uk/settings.php | 8 ++++++-- lang/uz/notifications.php | 2 ++ lang/uz/preferences.php | 1 + lang/uz/settings.php | 8 ++++++-- lang/vi/notifications.php | 2 ++ lang/vi/preferences.php | 1 + lang/vi/settings.php | 8 ++++++-- lang/zh_CN/notifications.php | 2 ++ lang/zh_CN/preferences.php | 1 + lang/zh_CN/settings.php | 8 ++++++-- lang/zh_TW/notifications.php | 2 ++ lang/zh_TW/preferences.php | 1 + lang/zh_TW/settings.php | 8 ++++++-- 162 files changed, 476 insertions(+), 119 deletions(-) diff --git a/lang/ar/notifications.php b/lang/ar/notifications.php index 30a49a631cf..69d5dfdcf52 100644 --- a/lang/ar/notifications.php +++ b/lang/ar/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'تم تحديث الصفحة: :pageName', 'updated_page_intro' => 'تم تحديث الصفحة في :appName:', 'updated_page_debounce' => 'لمنع تلقي عدد كبير من الإشعارات، لن يتم إرسال إشعارات إليك لفترة من الوقت لإجراء المزيد من التعديلات على هذه الصفحة بواسطة نفس المحرر.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'اسم الصفحة:', 'detail_page_path' => 'مسار الصفحة:', diff --git a/lang/ar/preferences.php b/lang/ar/preferences.php index 9158b4b375b..92733c9981b 100644 --- a/lang/ar/preferences.php +++ b/lang/ar/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'التحكم في إشعارات البريد الإلكتروني الذي تتلقاها عند إجراء نشاط معين داخل النظام.', 'notifications_opt_own_page_changes' => 'إشعاري عند حدوث تغييرات في الصفحات التي أملكها', 'notifications_opt_own_page_comments' => 'إشعاري بشأن التعليقات على الصفحات التي أملكها', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'إشعاري عند الردود على تعليقاتي', 'notifications_save' => 'حفظ اﻹعدادات', 'notifications_update_success' => 'تم تحديث إعدادات الإشعارات!', diff --git a/lang/ar/settings.php b/lang/ar/settings.php index 9006ac2277f..dc95ac8468f 100644 --- a/lang/ar/settings.php +++ b/lang/ar/settings.php @@ -75,7 +75,7 @@ 'reg_confirm_restrict_domain_placeholder' => 'لم يتم اختيار أي قيود', // Sorting Settings - 'sorting' => 'طريقة الترتيب', + 'sorting' => 'القوائم و الفرز', 'sorting_book_default' => 'ترتيب الكتاب الافتراضي', 'sorting_book_default_desc' => 'حدد قاعدة الترتيب الافتراضية لتطبيقها على الكتب الجديدة. لن يؤثر هذا على الكتب الحالية، ويمكن تجاوزه لكل كتاب على حدة.', 'sorting_rules' => 'قواعد الترتيب', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'تاريخ التحديث', 'sort_rule_op_chapters_first' => 'الفصول الأولى', 'sort_rule_op_chapters_last' => 'الفصول الأخيرة', + 'sorting_page_limits' => 'حدود العرض لكل صفحة', + 'sorting_page_limits_desc' => 'تعيين عدد العناصر لإظهار كل صفحة في قوائم مختلفة داخل النظام. عادةً ما يكون الرقم الأقل هو الأكثر أداء، بينما وضع رقم أعلى يغني عن النقر على صفحات متعددة. يوصى باستخدام مضاعفات رقم ٣ (18 و 24 و 30 و إلخ...).', // Maintenance settings 'maint' => 'الصيانة', @@ -195,11 +197,13 @@ 'role_import_content' => 'استيراد المحتوى', 'role_editor_change' => 'تغيير محرر الصفحة', 'role_notifications' => 'تلقي الإشعارات وإدارتها', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'أذونات الأصول', 'roles_system_warning' => 'اعلم أن الوصول إلى أي من الأذونات الثلاثة المذكورة أعلاه يمكن أن يسمح للمستخدم بتغيير امتيازاته الخاصة أو امتيازات الآخرين في النظام. قم بتعيين الأدوار مع هذه الأذونات فقط للمستخدمين الموثوق بهم.', 'role_asset_desc' => 'تتحكم هذه الأذونات في الوصول الافتراضي إلى الأصول داخل النظام. ستتجاوز الأذونات الخاصة بالكتب والفصول والصفحات هذه الأذونات.', 'role_asset_admins' => 'يُمنح المسؤولين حق الوصول تلقائيًا إلى جميع المحتويات ولكن هذه الخيارات قد تعرض خيارات واجهة المستخدم أو تخفيها.', 'role_asset_image_view_note' => 'يتعلق هذا بالرؤية داخل مدير الصور. يعتمد الوصول الفعلي لملفات الصور المُحمّلة على خِيار تخزين الصور في النظام.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'الكل', 'role_own' => 'ما يخص', 'role_controlled_by_asset' => 'يتحكم فيها الأصول التي يتم رفعها إلى', diff --git a/lang/bg/notifications.php b/lang/bg/notifications.php index 1afd23f1dc4..563ac24e84d 100644 --- a/lang/bg/notifications.php +++ b/lang/bg/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Updated page: :pageName', 'updated_page_intro' => 'A page has been updated in :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Page Name:', 'detail_page_path' => 'Page Path:', diff --git a/lang/bg/preferences.php b/lang/bg/preferences.php index f954340e2d2..8f5aaa07e90 100644 --- a/lang/bg/preferences.php +++ b/lang/bg/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/bg/settings.php b/lang/bg/settings.php index 6dd27d74a57..ae770c559cf 100644 --- a/lang/bg/settings.php +++ b/lang/bg/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Няма наложени ограничения', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Поддръжка', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Change page editor', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Настройки за достъп до активи', 'roles_system_warning' => 'Важно: Добавянето на потребител в някое от горните три роли може да му позволи да промени собствените си права или правата на другите в системата. Възлагайте тези роли само на доверени потребители.', 'role_asset_desc' => 'Тези настройки за достъп контролират достъпа по подразбиране до активите в системата. Настройките за достъп до книги, глави и страници ще отменят тези настройки.', 'role_asset_admins' => 'Администраторите автоматично получават достъп до цялото съдържание, но тези опции могат да показват или скриват опциите за потребителския интерфейс.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Всички', 'role_own' => 'Собствени', 'role_controlled_by_asset' => 'Контролирани от актива, към който са качени', diff --git a/lang/bn/activities.php b/lang/bn/activities.php index 7e4ba8229aa..c9268f8abe4 100644 --- a/lang/bn/activities.php +++ b/lang/bn/activities.php @@ -131,7 +131,7 @@ 'sort_rule_create' => 'created sort rule', 'sort_rule_create_notification' => 'Sort rule successfully created', 'sort_rule_update' => 'updated sort rule', - 'sort_rule_update_notification' => 'Sort rule successfully updated', + 'sort_rule_update_notification' => 'রোলটি সার্থকভাবে হালনাগাদ করা হয়েছে', 'sort_rule_delete' => 'deleted sort rule', 'sort_rule_delete_notification' => 'Sort rule successfully deleted', diff --git a/lang/bn/auth.php b/lang/bn/auth.php index 06ed376a71a..30879fcb6d3 100644 --- a/lang/bn/auth.php +++ b/lang/bn/auth.php @@ -24,8 +24,8 @@ 'password_hint' => 'ন্যূনতম ৮ অক্ষরের হতে হবে', 'forgot_password' => 'পাসওয়ার্ড ভুলে গেছেন?', 'remember_me' => 'লগইন স্থায়িত্ব ধরে রাখুন', - 'ldap_email_hint' => 'Please enter an email to use for this account.', - 'create_account' => 'Create Account', + 'ldap_email_hint' => 'অনুগ্রহ করে এই অ্যাকাউন্টের জন্য ব্যবহার করার জন্য একটি ইমেইল ঠিকানা লিখুন।', + 'create_account' => 'অ্যাকাউন্ট তৈরি করুন', 'already_have_account' => 'Already have an account?', 'dont_have_account' => 'Don\'t have an account?', 'social_login' => 'Social Login', @@ -39,16 +39,16 @@ 'register_success' => 'Thanks for signing up! You are now registered and signed in.', // Login auto-initiation - 'auto_init_starting' => 'Attempting Login', + 'auto_init_starting' => 'লগইন করার চেষ্টা করা হচ্ছে', 'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.', 'auto_init_start_link' => 'Proceed with authentication', // Password Reset - 'reset_password' => 'Reset Password', + 'reset_password' => 'পাসওয়ার্ড রিসেট করুন', 'reset_password_send_instructions' => 'Enter your email below and you will be sent an email with a password reset link.', 'reset_password_send_button' => 'Send Reset Link', 'reset_password_sent' => 'A password reset link will be sent to :email if that email address is found in the system.', - 'reset_password_success' => 'Your password has been successfully reset.', + 'reset_password_success' => 'আপনার পাসওয়ার্ড সফলভাবে রিসেট করা হয়েছে.', 'email_reset_subject' => 'Reset your :appName password', 'email_reset_text' => 'You are receiving this email because we received a password reset request for your account.', 'email_reset_not_requested' => 'If you did not request a password reset, no further action is required.', diff --git a/lang/bn/notifications.php b/lang/bn/notifications.php index 1afd23f1dc4..563ac24e84d 100644 --- a/lang/bn/notifications.php +++ b/lang/bn/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Updated page: :pageName', 'updated_page_intro' => 'A page has been updated in :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Page Name:', 'detail_page_path' => 'Page Path:', diff --git a/lang/bn/preferences.php b/lang/bn/preferences.php index 2e47604e471..c59ec62daeb 100644 --- a/lang/bn/preferences.php +++ b/lang/bn/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/bn/settings.php b/lang/bn/settings.php index 04aca4f2e0f..6d0f4ab88b2 100644 --- a/lang/bn/settings.php +++ b/lang/bn/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'No restriction set', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Maintenance', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Change page editor', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Asset Permissions', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', 'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'All', 'role_own' => 'Own', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', diff --git a/lang/bs/notifications.php b/lang/bs/notifications.php index 1afd23f1dc4..563ac24e84d 100644 --- a/lang/bs/notifications.php +++ b/lang/bs/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Updated page: :pageName', 'updated_page_intro' => 'A page has been updated in :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Page Name:', 'detail_page_path' => 'Page Path:', diff --git a/lang/bs/preferences.php b/lang/bs/preferences.php index 2872f5f3c65..f4459d738e4 100644 --- a/lang/bs/preferences.php +++ b/lang/bs/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/bs/settings.php b/lang/bs/settings.php index 81c2c0a93c3..c68605fe1f8 100644 --- a/lang/bs/settings.php +++ b/lang/bs/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'No restriction set', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Maintenance', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Change page editor', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Asset Permissions', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', 'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'All', 'role_own' => 'Own', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', diff --git a/lang/ca/notifications.php b/lang/ca/notifications.php index 49b986ad063..e3fb928a1ef 100644 --- a/lang/ca/notifications.php +++ b/lang/ca/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'S’ha actualitzat la pàgina :pageName', 'updated_page_intro' => 'S’ha actualitzat una pàgina a :appName.', 'updated_page_debounce' => 'Per a evitar que s’acumulin les notificacions, durant un temps no se us notificarà cap canvi fet en aquesta pàgina pel mateix usuari.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Nom de la pàgina:', 'detail_page_path' => 'Ruta de la pàgina:', diff --git a/lang/ca/preferences.php b/lang/ca/preferences.php index 25206726ecd..09fdaa4e586 100644 --- a/lang/ca/preferences.php +++ b/lang/ca/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Gestioneu les notificacions de correu electrònic que rebreu quan es facin certes activitats.', 'notifications_opt_own_page_changes' => 'Notifica’m els canvis a les meves pàgines.', 'notifications_opt_own_page_comments' => 'Notifica’m la creació de comentaris a les meves pàgines.', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notifica’m les respostes als meus comentaris.', 'notifications_save' => 'Desa les preferències', 'notifications_update_success' => 'S’han actualitzat les preferències de notificació', diff --git a/lang/ca/settings.php b/lang/ca/settings.php index 63352147e0b..352291fe5b5 100644 --- a/lang/ca/settings.php +++ b/lang/ca/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'No hi ha cap restricció', // Sorting Settings - 'sorting' => 'Ordenar', - 'sorting_book_default' => 'Ordre predeterminat del llibre', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Selecciona la regla d\'ordenació predeterminada per aplicar a nous llibres. Això no afectarà els llibres existents, i pot ser anul·lat per llibre.', 'sorting_rules' => 'Regles d\'ordenació', 'sorting_rules_desc' => 'Són operacions d\'ordenació predefinides que es poden aplicar al contingut en el sistema.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Data d\'actualització', 'sort_rule_op_chapters_first' => 'Capítols a l\'inici', 'sort_rule_op_chapters_last' => 'Capítols al final', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Manteniment', @@ -195,11 +197,13 @@ 'role_import_content' => 'Importar contingut', 'role_editor_change' => 'Canvi de l’editor de pàgina', 'role_notifications' => 'Recepció i gestió de notificacions', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Permisos de recursos', 'roles_system_warning' => 'Tingueu en compte que l’accés a qualsevol dels tres permisos de dalt permeten que l’usuari canviï els seus privilegis i els privilegis d’altres usuaris. Assigneu rols d’usuari amb aquests permisos només a usuaris de confiança.', 'role_asset_desc' => 'Aquests permisos controlen l’accés per defecte als recursos del sistema. El permisos dels llibres, capítols i pàgines sobreescriuran aquests permisos.', 'role_asset_admins' => 'Als administradors se’ls dona accés automàticament a tot el contingut però aquestes opcions mostren o amaguen opcions de la interfície d’usuari.', 'role_asset_image_view_note' => 'Això té relació amb la visibilitat al gestor d’imatges. L’accés a les imatges pujades dependrà de l’opció d’emmagatzematge d’imatges dels sistema.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Tot', 'role_own' => 'Propi', 'role_controlled_by_asset' => 'Controlat pel recurs a què estan pujats', diff --git a/lang/cs/notifications.php b/lang/cs/notifications.php index caf75f17951..a6c9e88b562 100644 --- a/lang/cs/notifications.php +++ b/lang/cs/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Aktualizovaná stránka: :pageName', 'updated_page_intro' => 'V :appName byla aktualizována stránka:', 'updated_page_debounce' => 'Po nějakou dobu neobdržíte další oznámení o aktualizaci této stránky stejným editorem, aby se omezil počet stejných zpráv.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Název stránky:', 'detail_page_path' => 'Umístění:', diff --git a/lang/cs/preferences.php b/lang/cs/preferences.php index ec7afaf276b..4a9bf3ad71a 100644 --- a/lang/cs/preferences.php +++ b/lang/cs/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Nastavte si e-mailová oznámení, která dostanete při provedení určitých akcí v systému.', 'notifications_opt_own_page_changes' => 'Upozornit na změny stránek u kterých jsem vlastníkem', 'notifications_opt_own_page_comments' => 'Upozornit na komentáře na stránkách, které vlastním', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Upozornit na odpovědi na mé komentáře', 'notifications_save' => 'Uložit nastavení', 'notifications_update_success' => 'Nastavení oznámení byla aktualizována!', diff --git a/lang/cs/settings.php b/lang/cs/settings.php index e72d4b403ad..f856b64cfac 100644 --- a/lang/cs/settings.php +++ b/lang/cs/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Žádná omezení nebyla nastavena', // Sorting Settings - 'sorting' => 'Řazení', - 'sorting_book_default' => 'Výchozí řazení knih', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Vybere výchozí pravidlo řazení pro nové knihy. Řazení neovlivní existující knihy a může být upraveno u konkrétní knihy.', 'sorting_rules' => 'Pravidla řazení', 'sorting_rules_desc' => 'Toto jsou předem definovaná pravidla řazení, která mohou být použita na webu.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Datum aktualizace', 'sort_rule_op_chapters_first' => 'Kapitoly jako první', 'sort_rule_op_chapters_last' => 'Kapitoly jako poslední', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Údržba', @@ -195,11 +197,13 @@ 'role_import_content' => 'Importovat obsah', 'role_editor_change' => 'Změnit editor stránek', 'role_notifications' => 'Přijímat a spravovat oznámení', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Obsahová oprávnění', 'roles_system_warning' => 'Berte na vědomí, že přístup k některému ze tří výše uvedených oprávnění může uživateli umožnit změnit svá vlastní oprávnění nebo oprávnění ostatních uživatelů v systému. Přiřazujte role s těmito oprávněními pouze důvěryhodným uživatelům.', 'role_asset_desc' => 'Tato oprávnění řídí přístup k obsahu napříč systémem. Specifická oprávnění na knihách, kapitolách a stránkách převáží tato nastavení.', 'role_asset_admins' => 'Administrátoři automaticky dostávají přístup k veškerému obsahu, ale tyto volby mohou ukázat nebo skrýt volby v uživatelském rozhraní.', 'role_asset_image_view_note' => 'To se týká viditelnosti ve správci obrázků. Skutečný přístup k nahraným souborům obrázků bude záviset na možnosti uložení systémových obrázků.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Vše', 'role_own' => 'Vlastní', 'role_controlled_by_asset' => 'Řídí se obsahem, do kterého jsou nahrávány', diff --git a/lang/cy/notifications.php b/lang/cy/notifications.php index 26f65f1447d..766e666b929 100644 --- a/lang/cy/notifications.php +++ b/lang/cy/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Tudalen wedi\'i diweddaru :pageName', 'updated_page_intro' => 'Mae tudalen newydd wedi cael ei diweddaru yn :appName:', 'updated_page_debounce' => 'Er mwyn atal llu o hysbysiadau, am gyfnod ni fyddwch yn cael hysbysiadau am ragor o olygiadau i\'r dudalen hon gan yr un golygydd.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Enw\'r dudalen:', 'detail_page_path' => 'Llwybr Tudalen:', diff --git a/lang/cy/preferences.php b/lang/cy/preferences.php index 6e00adfe8ee..4a08ca49886 100644 --- a/lang/cy/preferences.php +++ b/lang/cy/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Rheoli’r hysbysiadau e-bost a gewch pan fydd gweithgaredd penodol yn cael ei gyflawni o fewn y system.', 'notifications_opt_own_page_changes' => 'Hysbysu am newidiadau i dudalennau yr wyf yn berchen arnynt', 'notifications_opt_own_page_comments' => 'Hysbysu am sylwadau ar dudalennau yr wyf yn berchen arnynt', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Hysbysu am atebion i\'m sylwadau', 'notifications_save' => 'Dewisiadau Cadw', 'notifications_update_success' => 'Mae’r dewisiadau hysbysu wedi\'u diweddaru!', diff --git a/lang/cy/settings.php b/lang/cy/settings.php index cb08e8c9068..29e86e28bb5 100644 --- a/lang/cy/settings.php +++ b/lang/cy/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Ni osodwyd cyfyngiad', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Cynnal', @@ -195,11 +197,13 @@ 'role_import_content' => 'Mewnforio Cynnwys', 'role_editor_change' => 'Newid golygydd tudalen', 'role_notifications' => 'Derbyn a rheoli hysbysiadau', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Caniatâd Asedau', 'roles_system_warning' => 'Byddwch yn ymwybodol y gall mynediad i unrhyw un o\'r tri chaniatâd uchod ganiatáu i ddefnyddiwr newid eu breintiau eu hunain neu freintiau eraill yn y system. Neilltuo rolau gyda\'r caniatâd hyn i ddefnyddwyr dibynadwy yn unig.', 'role_asset_desc' => 'Mae\'r caniatâd hwn yn rheoli mynediad diofyn i\'r asedau o fewn y system. Bydd caniatâd ar Lyfrau, Penodau a Thudalennau yn diystyru\'r caniatâd hwn.', 'role_asset_admins' => 'Mae gweinyddwyr yn cael mynediad awtomatig i\'r holl gynnwys ond gall yr opsiynau hyn ddangos neu guddio opsiynau UI.', 'role_asset_image_view_note' => 'Mae hyn yn ymwneud â gwelededd o fewn y rheolwr delweddau. Bydd mynediad gwirioneddol i ffeiliau delwedd wedi\'u huwchlwytho yn dibynnu ar opsiwn storio delwedd y system.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Popeth', 'role_own' => 'Meddu', 'role_controlled_by_asset' => 'Wedi\'u rheoli gan yr ased y maent yn cael eu huwchlwytho iddo', diff --git a/lang/da/notifications.php b/lang/da/notifications.php index 8074c03c4d9..3aaf2ff1818 100644 --- a/lang/da/notifications.php +++ b/lang/da/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Opdateret side: :pageName', 'updated_page_intro' => 'En side er blevet opdateret i :appName:', 'updated_page_debounce' => 'For at forhindre en masse af notifikationer, i et stykke tid vil du ikke blive sendt notifikationer for yderligere redigeringer til denne side af den samme editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Sidens navn:', 'detail_page_path' => 'Sidesti:', diff --git a/lang/da/preferences.php b/lang/da/preferences.php index 143405a163c..5ba4f450cab 100644 --- a/lang/da/preferences.php +++ b/lang/da/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Administrer de e-mail-notifikationer, du modtager, når visse aktiviteter udføres i systemet.', 'notifications_opt_own_page_changes' => 'Adviser ved ændringer af sider, jeg ejer', 'notifications_opt_own_page_comments' => 'Adviser ved kommentarer på sider, jeg ejer', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Adviser ved svar på mine kommentarer', 'notifications_save' => 'Gem indstillinger', 'notifications_update_success' => 'Indstillinger for notifikationer er blevet opdateret!', diff --git a/lang/da/settings.php b/lang/da/settings.php index 4f194545b75..8d89c30af2d 100644 --- a/lang/da/settings.php +++ b/lang/da/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Ingen restriktion opsat', // Sorting Settings - 'sorting' => 'Sortering', - 'sorting_book_default' => 'Standard bog-sortering', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Vælg den standardsorteringsregel, der skal gælde for nye bøger. Dette påvirker ikke eksisterende bøger og kan tilsidesættes for hver enkelt bog.', 'sorting_rules' => 'Regler for sortering', 'sorting_rules_desc' => 'Det er foruddefinerede sorteringsoperationer, som kan anvendes på indhold i systemet.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Opdateret dato', 'sort_rule_op_chapters_first' => 'Kapitler først', 'sort_rule_op_chapters_last' => 'De sidste kapitler', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Vedligeholdelse', @@ -195,11 +197,13 @@ 'role_import_content' => 'Importer indhold', 'role_editor_change' => 'Skift side editor', 'role_notifications' => 'Modtag og administrer notifikationer', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Tilladelser for medier og "assets"', 'roles_system_warning' => 'Vær opmærksom på, at adgang til alle af de ovennævnte tre tilladelser, kan give en bruger mulighed for at ændre deres egne brugerrettigheder eller brugerrettigheder for andre i systemet. Tildel kun roller med disse tilladelser til betroede brugere.', 'role_asset_desc' => 'Disse tilladelser kontrollerer standardadgang til medier og "assets" i systemet. Tilladelser til bøger, kapitler og sider tilsidesætter disse tilladelser.', 'role_asset_admins' => 'Administratorer får automatisk adgang til alt indhold, men disse indstillinger kan vise eller skjule UI-indstillinger.', 'role_asset_image_view_note' => 'Dette vedrører synlighed i billedhåndteringen. Den faktiske adgang til uploadede billedfiler vil afhænge af systemets billedlagringsindstilling.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Alle', 'role_own' => 'Eget', 'role_controlled_by_asset' => 'Styres af det medie/"asset", de uploades til', diff --git a/lang/de/notifications.php b/lang/de/notifications.php index f3b86b5023a..71b71d1b662 100644 --- a/lang/de/notifications.php +++ b/lang/de/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Aktualisierte Seite: :pageName', 'updated_page_intro' => 'Eine Seite wurde in :appName aktualisiert:', 'updated_page_debounce' => 'Um eine Flut von Benachrichtigungen zu vermeiden, werden Sie für eine gewisse Zeit keine Benachrichtigungen für weitere Bearbeitungen dieser Seite durch denselben Bearbeiter erhalten.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Name der Seite:', 'detail_page_path' => 'Seitenpfad:', diff --git a/lang/de/preferences.php b/lang/de/preferences.php index 26f0b05dacc..1f74f3d3eb0 100644 --- a/lang/de/preferences.php +++ b/lang/de/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Legen Sie fest, welche E-Mail-Benachrichtigungen Sie erhalten, wenn bestimmte Aktivitäten im System durchgeführt werden.', 'notifications_opt_own_page_changes' => 'Benachrichtigung bei Änderungen an eigenen Seiten', 'notifications_opt_own_page_comments' => 'Benachrichtigung bei Kommentaren an eigenen Seiten', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Bei Antworten auf meine Kommentare benachrichtigen', 'notifications_save' => 'Einstellungen speichern', 'notifications_update_success' => 'Benachrichtigungseinstellungen wurden aktualisiert!', diff --git a/lang/de/settings.php b/lang/de/settings.php index 250668e8a78..a874089958a 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -76,8 +76,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Keine Einschränkung gesetzt', // Sorting Settings - 'sorting' => 'Sortierung', - 'sorting_book_default' => 'Standard-Buchsortierung', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Wählen Sie die Standard-Sortierregel aus, die auf neue Bücher angewendet werden soll. Dies wirkt sich nicht auf bestehende Bücher aus und kann pro Buch überschrieben werden.', 'sorting_rules' => 'Sortierregeln', 'sorting_rules_desc' => 'Dies sind vordefinierte Sortieraktionen, die auf Inhalte im System angewendet werden können.', @@ -104,6 +104,8 @@ 'sort_rule_op_updated_date' => 'Aktualisierungsdatum', 'sort_rule_op_chapters_first' => 'Kapitel zuerst', 'sort_rule_op_chapters_last' => 'Kapitel zuletzt', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Wartung', @@ -196,11 +198,13 @@ 'role_import_content' => 'Inhalt importieren', 'role_editor_change' => 'Seiten-Editor ändern', 'role_notifications' => 'Empfangen und Verwalten von Benachrichtigungen', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Berechtigungen', 'roles_system_warning' => 'Beachten Sie, dass der Zugriff auf eine der oben genannten drei Berechtigungen einem Benutzer erlauben kann, seine eigenen Berechtigungen oder die Rechte anderer im System zu ändern. Weisen Sie nur Rollen, mit diesen Berechtigungen, vertrauenswürdigen Benutzern zu.', 'role_asset_desc' => 'Diese Berechtigungen gelten für den Standard-Zugriff innerhalb des Systems. Berechtigungen für Bücher, Kapitel und Seiten überschreiben diese Berechtigungenen.', 'role_asset_admins' => 'Administratoren erhalten automatisch Zugriff auf alle Inhalte, aber diese Optionen können Oberflächenoptionen ein- oder ausblenden.', 'role_asset_image_view_note' => 'Das bezieht sich auf die Sichtbarkeit innerhalb des Bildmanagers. Der tatsächliche Zugriff auf hochgeladene Bilddateien hängt von der Speicheroption des Systems für Bilder ab.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Alle', 'role_own' => 'Eigene', 'role_controlled_by_asset' => 'Berechtigungen werden vom Uploadziel bestimmt', diff --git a/lang/de_informal/notifications.php b/lang/de_informal/notifications.php index 0bf7739f4da..99c270ec1be 100644 --- a/lang/de_informal/notifications.php +++ b/lang/de_informal/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Aktualisierte Seite: :pageName', 'updated_page_intro' => 'Eine Seite wurde in :appName aktualisiert:', 'updated_page_debounce' => 'Um eine Flut von Benachrichtigungen zu vermeiden, wirst du für eine gewisse Zeit keine Benachrichtigungen für weitere Bearbeitungen dieser Seite durch denselben Bearbeiter erhalten.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Seitenname:', 'detail_page_path' => 'Seitenpfad:', diff --git a/lang/de_informal/preferences.php b/lang/de_informal/preferences.php index 07b83a8da3a..bfb57b2f492 100644 --- a/lang/de_informal/preferences.php +++ b/lang/de_informal/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Lege fest, welche E-Mail-Benachrichtigungen du erhältst, wenn bestimmte Aktivitäten im System durchgeführt werden.', 'notifications_opt_own_page_changes' => 'Benachrichtigung bei Änderungen an eigenen Seiten', 'notifications_opt_own_page_comments' => 'Benachrichtigung bei Kommentaren an eigenen Seiten', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Bei Antworten auf meine Kommentare benachrichtigen', 'notifications_save' => 'Einstellungen speichern', 'notifications_update_success' => 'Benachrichtigungseinstellungen wurden aktualisiert!', diff --git a/lang/de_informal/settings.php b/lang/de_informal/settings.php index 50003c58ee7..ab9a075a0a7 100644 --- a/lang/de_informal/settings.php +++ b/lang/de_informal/settings.php @@ -76,8 +76,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Keine Einschränkung gesetzt', // Sorting Settings - 'sorting' => 'Sortierung', - 'sorting_book_default' => 'Standard-Buchsortierung', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Wähle die Standard-Sortierregel aus, die auf neue Bücher angewendet werden soll. Dies wirkt sich nicht auf bestehende Bücher aus und kann pro Buch überschrieben werden.', 'sorting_rules' => 'Sortierregeln', 'sorting_rules_desc' => 'Dies sind vordefinierte Sortieraktionen, die auf Inhalte im System angewendet werden können.', @@ -104,6 +104,8 @@ 'sort_rule_op_updated_date' => 'Aktualisierungsdatum', 'sort_rule_op_chapters_first' => 'Kapitel zuerst', 'sort_rule_op_chapters_last' => 'Kapitel zuletzt', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Wartung', @@ -196,11 +198,13 @@ 'role_import_content' => 'Inhalt importieren', 'role_editor_change' => 'Seiteneditor ändern', 'role_notifications' => 'Empfangen und Verwalten von Benachrichtigungen', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Berechtigungen', 'roles_system_warning' => 'Beachte, dass der Zugriff auf eine der oben genannten drei Berechtigungen einem Benutzer erlauben kann, seine eigenen Berechtigungen oder die Rechte anderer im System zu ändern. Weise nur Rollen mit diesen Berechtigungen vertrauenswürdigen Benutzern zu.', 'role_asset_desc' => 'Diese Berechtigungen gelten für den Standard-Zugriff innerhalb des Systems. Berechtigungen für Bücher, Kapitel und Seiten überschreiben diese Berechtigungen.', 'role_asset_admins' => 'Administratoren erhalten automatisch Zugriff auf alle Inhalte, aber diese Optionen können Oberflächenoptionen ein- oder ausblenden.', 'role_asset_image_view_note' => 'Das bezieht sich auf die Sichtbarkeit innerhalb des Bildmanagers. Der tatsächliche Zugriff auf hochgeladene Bilddateien hängt von der Speicheroption des Systems für Bilder ab.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Alle', 'role_own' => 'Eigene', 'role_controlled_by_asset' => 'Berechtigungen werden vom Uploadziel bestimmt', diff --git a/lang/el/notifications.php b/lang/el/notifications.php index 3924cab3b5c..e9a9fe15de1 100644 --- a/lang/el/notifications.php +++ b/lang/el/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Ενημερωμένη σελίδα: :pageName', 'updated_page_intro' => 'Μια σελίδα έχει ενημερωθεί στο :appName:', 'updated_page_debounce' => 'Για να αποτρέψετε μαζικές ειδοποιήσεις, για κάποιο διάστημα δε θα σας αποστέλλονται ειδοποιήσεις για περαιτέρω αλλαγές σε αυτήν τη σελίδα από τον ίδιο συντάκτη.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Όνομα σελίδας:', 'detail_page_path' => 'Διαδρομή σελίδας:', diff --git a/lang/el/preferences.php b/lang/el/preferences.php index 4f7e97d5257..2c2ce8fe95f 100644 --- a/lang/el/preferences.php +++ b/lang/el/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/el/settings.php b/lang/el/settings.php index 172038fd014..67461604eb0 100644 --- a/lang/el/settings.php +++ b/lang/el/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Δε έχουν ρυθμιστεί περιορισμοί ακόμα', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Συντήρηση', @@ -195,11 +197,13 @@ 'role_import_content' => 'Εισαγωγή περιεχομένου', 'role_editor_change' => 'Αλλαγή προγράμματος επεξεργασίας σελίδας', 'role_notifications' => 'Λήψη & διαχείριση ειδοποιήσεων', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Δικαιώματα Συστήματος', 'roles_system_warning' => 'Λάβετε υπόψη ότι η πρόσβαση σε οποιοδήποτε από τις τρεις παραπάνω άδειες (δικαιώματα) μπορεί να επιτρέψει σε έναν χρήστη να αλλάξει τα δικά του προνόμια ή τα προνόμια άλλων στο σύστημα. Εκχωρήστε ρόλους με αυτά τα δικαιώματα μόνο σε αξιόπιστους χρήστες.', 'role_asset_desc' => 'Αυτά τα δικαιώματα ελέγχουν την προεπιλεγμένη πρόσβαση στα στοιχεία (άδειες) εντός του συστήματος. Τα δικαιώματα σε Βιβλία, Κεφάλαια και Σελίδες θα παρακάμψουν αυτές τις άδειες.', 'role_asset_admins' => 'Οι διαχειριστές έχουν αυτόματα πρόσβαση σε όλο το περιεχόμενο, αλλά αυτές οι επιλογές ενδέχεται να εμφανίζουν ή να αποκρύπτουν τις επιλογές διεπαφής χρήστη.', 'role_asset_image_view_note' => 'Αυτό σχετίζεται με την ορατότητα εντός του διαχειριστή εικόνων. Η πραγματική πρόσβαση των μεταφορτωμένων αρχείων εικόνας θα εξαρτηθεί από την επιλογή αποθήκευσης εικόνας συστήματος.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Ολα', 'role_own' => 'Τα δικά του', 'role_controlled_by_asset' => 'Ελέγχονται από το στοιχείο στο οποίο ανεβαίνουν (Ράφια, Βιβλία)', diff --git a/lang/es/notifications.php b/lang/es/notifications.php index 5ebc42129d9..38c7708884f 100644 --- a/lang/es/notifications.php +++ b/lang/es/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Página actualizada: :pageName', 'updated_page_intro' => 'Una página ha sido actualizada en :appName:', 'updated_page_debounce' => 'Para prevenir notificaciones en masa, durante un tiempo no se enviarán notificaciones para futuras ediciones de esta página por el mismo editor.', + 'comment_mention_subject' => 'Ha sido mencionado en un comentario en la página: :pageName', + 'comment_mention_intro' => 'Fue mencionado en un comentario en :appName:', 'detail_page_name' => 'Nombre de página:', 'detail_page_path' => 'Ruta de la página:', diff --git a/lang/es/preferences.php b/lang/es/preferences.php index 13af93d2978..0328a572acd 100644 --- a/lang/es/preferences.php +++ b/lang/es/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Controle las notificaciones por correo electrónico que recibe cuando se realiza cierta actividad dentro del sistema.', 'notifications_opt_own_page_changes' => 'Notificar sobre los cambios en las páginas en las que soy propietario', 'notifications_opt_own_page_comments' => 'Notificar sobre comentarios en las páginas en las que soy propietario', + 'notifications_opt_comment_mentions' => 'Notificarme cuando he sido mencionado en un comentario', 'notifications_opt_comment_replies' => 'Notificar sobre respuestas a mis comentarios', 'notifications_save' => 'Guardar preferencias', 'notifications_update_success' => '¡Se han actualizado las preferencias de notificaciones!', diff --git a/lang/es/settings.php b/lang/es/settings.php index 55eb498d630..1a9927c8eca 100644 --- a/lang/es/settings.php +++ b/lang/es/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Ninguna restricción establecida', // Sorting Settings - 'sorting' => 'Ordenación', - 'sorting_book_default' => 'Orden por defecto del libro', + 'sorting' => 'Listas y ordenación', + 'sorting_book_default' => 'Orden de libros por defecto', 'sorting_book_default_desc' => 'Seleccione la regla de ordenación predeterminada para aplicar a nuevos libros. Esto no afectará a los libros existentes, y puede ser anulado por libro.', 'sorting_rules' => 'Reglas de ordenación', 'sorting_rules_desc' => 'Son operaciones de ordenación predefinidas que se pueden aplicar al contenido en el sistema.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Fecha de actualización', 'sort_rule_op_chapters_first' => 'Capítulos al inicio', 'sort_rule_op_chapters_last' => 'Capítulos al final', + 'sorting_page_limits' => 'Límites de visualización por página', + 'sorting_page_limits_desc' => 'Establecer cuántos elementos a mostrar por página en varias listas dentro del sistema. Normalmente una cantidad más baja rendirá mejor, mientras que una cantidad más alta evita la necesidad de hacer clic a través de varias páginas. Se recomienda utilizar un múltiplo par de 3 (18, 24, 30, etc).', // Maintenance settings 'maint' => 'Mantenimiento', @@ -195,11 +197,13 @@ 'role_import_content' => 'Importar contenido', 'role_editor_change' => 'Cambiar editor de página', 'role_notifications' => 'Recibir y gestionar notificaciones', + 'role_permission_note_users_and_roles' => 'Estos permisos proporcionarán también visibilidad y búsqueda de usuarios y roles en el sistema.', 'role_asset' => 'Permisos de contenido', 'roles_system_warning' => 'Tenga en cuenta que el acceso a cualquiera de los tres permisos anteriores puede permitir a un usuario alterar sus propios privilegios o los privilegios de otros en el sistema. Sólo asignar roles con estos permisos a usuarios de confianza.', 'role_asset_desc' => 'Estos permisos controlan el acceso por defecto a los contenidos del sistema. Los permisos de Libros, Capítulos y Páginas sobreescribiran estos permisos.', 'role_asset_admins' => 'A los administradores se les asigna automáticamente permisos para acceder a todo el contenido pero estas opciones podrían mostrar u ocultar opciones de la interfaz.', 'role_asset_image_view_note' => 'Esto se refiere a la visibilidad dentro del gestor de imágenes. El acceso a los archivos de imagen subidos dependerá de la opción de almacenamiento de imágenes del sistema.', + 'role_asset_users_note' => 'Estos permisos proporcionarán también visibilidad y búsqueda de usuarios en el sistema.', 'role_all' => 'Todo', 'role_own' => 'Propio', 'role_controlled_by_asset' => 'Controlado por el contenido al que ha sido subido', diff --git a/lang/es_AR/notifications.php b/lang/es_AR/notifications.php index fc89b2c25ab..af9f5332b36 100644 --- a/lang/es_AR/notifications.php +++ b/lang/es_AR/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Página actualizada: :pageName', 'updated_page_intro' => 'Se actualizó una página en :appName:', 'updated_page_debounce' => 'Para evitar una avalancha de notificaciones, durante un tiempo no se enviarán notificaciones sobre más ediciones de esta página por el mismo editor.', + 'comment_mention_subject' => 'Ha sido mencionado en un comentario en la página: :pageName', + 'comment_mention_intro' => 'Fue mencionado en un comentario en :appName:', 'detail_page_name' => 'Nombre de la página:', 'detail_page_path' => 'Ruta de la página:', diff --git a/lang/es_AR/preferences.php b/lang/es_AR/preferences.php index 11c872021c0..deca55466b4 100644 --- a/lang/es_AR/preferences.php +++ b/lang/es_AR/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Controle las notificaciones por correo electrónico que recibe cuando se realiza cierta actividad dentro del sistema.', 'notifications_opt_own_page_changes' => 'Notificar sobre los cambios en las páginas de las que soy propietario', 'notifications_opt_own_page_comments' => 'Notificar sobre comentarios en las páginas de las que soy propietario', + 'notifications_opt_comment_mentions' => 'Notificarme cuando he sido mencionado en un comentario', 'notifications_opt_comment_replies' => 'Notificar sobre respuestas a mis comentarios', 'notifications_save' => 'Guardar preferencias', 'notifications_update_success' => '¡Se actualizaron las preferencias de notificaciones!', diff --git a/lang/es_AR/settings.php b/lang/es_AR/settings.php index 5cbcff86eae..3eb41d2cc91 100644 --- a/lang/es_AR/settings.php +++ b/lang/es_AR/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Ninguna restricción establecida', // Sorting Settings - 'sorting' => 'Ordenando', - 'sorting_book_default' => 'Orden predeterminado del libro', + 'sorting' => 'Listas y ordenación', + 'sorting_book_default' => 'Orden de libros por defecto', 'sorting_book_default_desc' => 'Seleccione la regla de ordenación predeterminada para aplicar a nuevos libros. Esto no afectará a los libros existentes, y puede ser anulado por libro.', 'sorting_rules' => 'Reglas de Ordenación', 'sorting_rules_desc' => 'Son operaciones de ordenación predefinidas que se pueden aplicar al contenido en el sistema.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Fecha de actualización', 'sort_rule_op_chapters_first' => 'Capítulos al inicio', 'sort_rule_op_chapters_last' => 'Capítulos al final', + 'sorting_page_limits' => 'Límites de visualización por página', + 'sorting_page_limits_desc' => 'Establecer cuántos elementos a mostrar por página en varias listas dentro del sistema. Normalmente una cantidad más baja rendirá mejor, mientras que una cantidad más alta evita la necesidad de hacer clic a través de varias páginas. Se recomienda utilizar un múltiplo par de 3 (18, 24, 30, etc).', // Maintenance settings 'maint' => 'Mantenimiento', @@ -196,11 +198,13 @@ 'role_import_content' => 'Importar contenido', 'role_editor_change' => 'Cambiar editor de página', 'role_notifications' => 'Recibir y gestionar notificaciones', + 'role_permission_note_users_and_roles' => 'Estos permisos proporcionarán también visibilidad y búsqueda de usuarios y roles en el sistema.', 'role_asset' => 'Permisos de activos', 'roles_system_warning' => 'Tenga en cuenta que el acceso a cualquiera de los tres permisos anteriores puede permitir a un usuario modificar sus propios privilegios o los privilegios de otros usuarios en el sistema. Asignar roles con estos permisos sólo a usuarios de comfianza.', 'role_asset_desc' => 'Estos permisos controlan el acceso por defecto a los activos del sistema. Permisos definidos en Libros, Capítulos y Páginas ignorarán estos permisos.', 'role_asset_admins' => 'Los administradores reciben automáticamente acceso a todo el contenido pero estas opciones pueden mostrar u ocultar opciones de UI.', 'role_asset_image_view_note' => 'Esto se refiere a la visibilidad dentro del gestor de imágenes. El acceso real a los archivos de imágenes subidos, dependerá de la opción de almacenamiento de imágenes del sistema.', + 'role_asset_users_note' => 'Estos permisos proporcionarán también visibilidad y búsqueda de usuarios en el sistema.', 'role_all' => 'Todo', 'role_own' => 'Propio', 'role_controlled_by_asset' => 'Controlado por el activo al que ha sido subido', diff --git a/lang/et/notifications.php b/lang/et/notifications.php index 0b5fc814c04..2843d5799b4 100644 --- a/lang/et/notifications.php +++ b/lang/et/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Muudetud leht: :pageName', 'updated_page_intro' => 'Rakenduses :appName muudeti lehte:', 'updated_page_debounce' => 'Et vältida liigseid teavitusi, ei saadeta sulle mõnda aega teavitusi selle lehe muutmiste kohta sama kasutaja poolt.', + 'comment_mention_subject' => 'Sind mainiti kommentaaris lehel: :pageName', + 'comment_mention_intro' => 'Sind mainiti kommentaaris rakenduses :appName:', 'detail_page_name' => 'Lehe nimetus:', 'detail_page_path' => 'Lehe asukoht:', diff --git a/lang/et/preferences.php b/lang/et/preferences.php index f4ba5e6aed7..a4003463c3c 100644 --- a/lang/et/preferences.php +++ b/lang/et/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Halda e-posti teavitusi, mis saadetakse teatud tegevuste puhul.', 'notifications_opt_own_page_changes' => 'Teavita muudatustest minu lehtedel', 'notifications_opt_own_page_comments' => 'Teavita kommentaaridest minu lehtedel', + 'notifications_opt_comment_mentions' => 'Teavita mind, kui mind kommentaaris mainitakse', 'notifications_opt_comment_replies' => 'Teavita vastustest minu kommentaaridele', 'notifications_save' => 'Salvesta eelistused', 'notifications_update_success' => 'Teavituste eelistused on salvestatud!', diff --git a/lang/et/settings.php b/lang/et/settings.php index 003238cfebe..fbd9d9c7e7c 100644 --- a/lang/et/settings.php +++ b/lang/et/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Piirangut ei ole', // Sorting Settings - 'sorting' => 'Sorteerimine', - 'sorting_book_default' => 'Vaikimisi raamatu sorteerimine', + 'sorting' => 'Loendid ja järjestamine', + 'sorting_book_default' => 'Vaikimisi raamatute sorteerimise reegel', 'sorting_book_default_desc' => 'Vali vaikimisi uutele raamatutele rakenduv sorteerimisreegel. See ei mõjuta olemasolevaid raamatuid ning seda saab raamatupõhiselt muuta.', 'sorting_rules' => 'Sorteerimisreeglid', 'sorting_rules_desc' => 'Need on eeldefineeritud sorteerimistoimingud, mida saab süsteemis olevale sisule rakendada.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Muutmise aeg', 'sort_rule_op_chapters_first' => 'Peatükid eespool', 'sort_rule_op_chapters_last' => 'Peatükid tagapool', + 'sorting_page_limits' => 'Leheküljepõhised kuvalimiidid', + 'sorting_page_limits_desc' => 'Seadista, mitut objekti erinevates loendites ühel leheküljel kuvada. Väiksem väärtus tähendab reeglina paremat jõudlust, samas kui suurem väärtus vähendab vajadust mitut lehekülge läbi klikkida. Soovituslik on kasutada 3-ga jaguvat väärtust (18, 24, 30 jne).', // Maintenance settings 'maint' => 'Hooldus', @@ -195,11 +197,13 @@ 'role_import_content' => 'Imporditud sisu', 'role_editor_change' => 'Lehe redaktori muutmine', 'role_notifications' => 'Võta vastu ja halda teavitusi', + 'role_permission_note_users_and_roles' => 'Need õigused lubavad ka süsteemis olevaid kasutajaid ja rolle vaadata ja otsida.', 'role_asset' => 'Sisu õigused', 'roles_system_warning' => 'Pane tähele, et ülalolevad kolm õigust võimaldavad kasutajal enda või teiste kasutajate õiguseid muuta. Määra nende õigustega roll ainult usaldusväärsetele kasutajatele.', 'role_asset_desc' => 'Need load kontrollivad vaikimisi ligipääsu süsteemis olevale sisule. Raamatute, peatükkide ja lehtede õigused rakenduvad esmajärjekorras.', 'role_asset_admins' => 'Administraatoritel on automaatselt ligipääs kogu sisule, aga need valikud võivad peida või näidata kasutajaliidese elemente.', 'role_asset_image_view_note' => 'See käib nähtavuse kohta pildifailide halduris. Tegelik ligipääs üleslaaditud pildifailidele sõltub süsteemsest piltide salvestamise valikust.', + 'role_asset_users_note' => 'Need õigused lubavad ka süsteemis olevaid kasutajaid vaadata ja otsida.', 'role_all' => 'Kõik', 'role_own' => 'Enda omad', 'role_controlled_by_asset' => 'Õigused määratud seotud objekti kaudu', diff --git a/lang/eu/notifications.php b/lang/eu/notifications.php index 1afd23f1dc4..563ac24e84d 100644 --- a/lang/eu/notifications.php +++ b/lang/eu/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Updated page: :pageName', 'updated_page_intro' => 'A page has been updated in :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Page Name:', 'detail_page_path' => 'Page Path:', diff --git a/lang/eu/preferences.php b/lang/eu/preferences.php index da7638593b5..3818932d4ea 100644 --- a/lang/eu/preferences.php +++ b/lang/eu/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/eu/settings.php b/lang/eu/settings.php index e93f3c96469..9a9227b8303 100644 --- a/lang/eu/settings.php +++ b/lang/eu/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Mugarik gabe', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Mantentze-lanak', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Change page editor', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Fitxategi baimenak', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', 'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Guztiak', 'role_own' => 'Norberarenak', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', diff --git a/lang/fa/notifications.php b/lang/fa/notifications.php index 4595fd8252a..d216b04fec6 100644 --- a/lang/fa/notifications.php +++ b/lang/fa/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'صفحه جدید: :pageName', 'updated_page_intro' => 'یک صفحه جدید ایجاد شده است در :appName:', 'updated_page_debounce' => 'برای جلوگیری از انبوه اعلان‌ها، برای مدتی اعلان‌ ویرایش‌هایی که توسط همان ویرایشگر در این صفحه انجام می‌شود، ارسال نخواهد شد.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'نام صفحه:', 'detail_page_path' => 'نام میسر صفحه:', diff --git a/lang/fa/preferences.php b/lang/fa/preferences.php index ceecedc8033..00d277bdfd5 100644 --- a/lang/fa/preferences.php +++ b/lang/fa/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'تنظیمات اطلاعیه‌های ایمیلی هنگام انجام فعالیت‌های خاص در سیستم.', 'notifications_opt_own_page_changes' => 'در صورت تغییرات در صفحاتی که متعلق به من است، اطلاع بده', 'notifications_opt_own_page_comments' => 'در صورت ثبت نظر در صفحاتی که متعلق به من است، اطلاع بده', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'پس از درج پاسخ به روی نظراتی که من ثبت کرده‌ام، اطلاع بده', 'notifications_save' => 'ذخیره تنظیمات', 'notifications_update_success' => 'تنظیمات اعلان‌ها به روز شده است!', diff --git a/lang/fa/settings.php b/lang/fa/settings.php index e4cb9018ebf..abbfce470c3 100644 --- a/lang/fa/settings.php +++ b/lang/fa/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'بدون محدودیت', // Sorting Settings - 'sorting' => 'مرتب‌سازی', - 'sorting_book_default' => 'مرتب‌سازی پیش‌فرض کتاب', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'قانون پیش‌فرض مرتب‌سازی را برای کتاب‌های جدید انتخاب کنید. تغییر قانون بر ترتیب کتاب‌های موجود تأثیری ندارد و می‌تواند برای هر کتاب به‌صورت جداگانه تغییر یابد.', 'sorting_rules' => 'قوانین مرتب‌سازی', 'sorting_rules_desc' => 'این‌ها عملیات مرتب‌سازی از پیش تعریف‌شده‌ای هستند که می‌توانید آن‌ها را بر محتوای سیستم اعمال کنید.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'تاریخ به‌روزرسانی', 'sort_rule_op_chapters_first' => 'ابتدا فصل‌ها', 'sort_rule_op_chapters_last' => 'فصل‌ها در آخر', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'نگهداری', @@ -195,11 +197,13 @@ 'role_import_content' => 'وارد کردن محتوا', 'role_editor_change' => 'تغییر ویرایشگر صفحه', 'role_notifications' => 'دریافت و مدیریت اعلان‌ها', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'مجوزهای دارایی', 'roles_system_warning' => 'توجه داشته باشید که دسترسی به هر یک از سه مجوز فوق می‌تواند به کاربر اجازه دهد تا امتیازات خود یا امتیازات دیگران را در سیستم تغییر دهد. فقط نقش هایی را با این مجوزها به کاربران مورد اعتماد اختصاص دهید.', 'role_asset_desc' => 'این مجوزها دسترسی پیش‌فرض به دارایی‌های درون سیستم را کنترل می‌کنند. مجوزهای مربوط به کتاب‌ها، فصل‌ها و صفحات این مجوزها را لغو می‌کنند.', 'role_asset_admins' => 'به ادمین‌ها به‌طور خودکار به همه محتوا دسترسی داده می‌شود، اما این گزینه‌ها ممکن است گزینه‌های UI را نشان داده یا پنهان کنند.', 'role_asset_image_view_note' => 'این مربوط به مرئی بودن در بخش مدیر تصاویر است. دسترسی عملی به تصاویر آپلود شده بستگی به گزینه ذخیره‌سازی تصویر سیستم دارد.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'همه', 'role_own' => 'صاحب', 'role_controlled_by_asset' => 'توسط دارایی که در آن آپلود می شود کنترل می شود', diff --git a/lang/fi/notifications.php b/lang/fi/notifications.php index a737ad7d9b4..647378ded83 100644 --- a/lang/fi/notifications.php +++ b/lang/fi/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Päivitetty sivu: :pageName', 'updated_page_intro' => 'Sivu on päivitetty sivustolla :appName:', 'updated_page_debounce' => 'Useiden ilmoitusten välttämiseksi sinulle ei toistaiseksi lähetetä ilmoituksia saman toimittajan tekemistä uusista muokkauksista tälle sivulle.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Sivun nimi:', 'detail_page_path' => 'Sivun polku:', diff --git a/lang/fi/preferences.php b/lang/fi/preferences.php index 25cd76bf781..05afc111f10 100644 --- a/lang/fi/preferences.php +++ b/lang/fi/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Hallitse järjestelmän toimintoihin liittyviä sähköposti-ilmoituksia.', 'notifications_opt_own_page_changes' => 'Ilmoita omistamilleni sivuille tehdyistä muutoksista', 'notifications_opt_own_page_comments' => 'Ilmoita omistamilleni sivuille tehdyistä kommenteista', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Ilmoita vastauksista kommentteihini', 'notifications_save' => 'Tallenna asetukset', 'notifications_update_success' => 'Ilmoitusasetukset on päivitetty!', diff --git a/lang/fi/settings.php b/lang/fi/settings.php index d9e91d4fa88..499122fe35b 100644 --- a/lang/fi/settings.php +++ b/lang/fi/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Ei rajoituksia', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Huolto', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Vaihda sivun editoria', 'role_notifications' => 'Vastaanota ja hallinnoi ilmoituksia', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Sisältöjen oikeudet', 'roles_system_warning' => 'Huomaa, että minkä tahansa edellä mainituista kolmesta käyttöoikeudesta voi antaa käyttäjälle mahdollisuuden muuttaa omia tai muiden järjestelmän käyttäjien oikeuksia. Anna näitä oikeuksia sisältävät roolit vain luotetuille käyttäjille.', 'role_asset_desc' => 'Näillä asetuksilla hallitaan oletuksena annettavia käyttöoikeuksia järjestelmässä oleviin sisältöihin. Yksittäisten kirjojen, lukujen ja sivujen käyttöoikeudet kumoavat nämä käyttöoikeudet.', 'role_asset_admins' => 'Ylläpitäjät saavat automaattisesti pääsyn kaikkeen sisältöön, mutta nämä vaihtoehdot voivat näyttää tai piilottaa käyttöliittymävalintoja.', 'role_asset_image_view_note' => 'Tämä tarkoittaa näkyvyyttä kuvien hallinnassa. Pääsy ladattuihin kuvatiedostoihin riippuu asetetusta kuvien tallennusvaihtoehdosta.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Kaikki', 'role_own' => 'Omat', 'role_controlled_by_asset' => 'Määräytyy sen sisällön mukaan, johon ne on ladattu', diff --git a/lang/fr/notifications.php b/lang/fr/notifications.php index 5d7ae88f72b..b82f82bd0b8 100644 --- a/lang/fr/notifications.php +++ b/lang/fr/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Page mise à jour: :pageName', 'updated_page_intro' => 'Une page a été mise à jour dans :appName:', 'updated_page_debounce' => 'Pour éviter de nombreuses notifications, pendant un certain temps, vous ne recevrez pas de notifications pour d\'autres modifications de cette page par le même éditeur.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Nom de la page :', 'detail_page_path' => 'Chemin de la page :', diff --git a/lang/fr/preferences.php b/lang/fr/preferences.php index 2180ed70c46..c595222f1b8 100644 --- a/lang/fr/preferences.php +++ b/lang/fr/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Contrôlez les notifications par e-mail que vous recevez lorsque certaines activités sont effectuées dans le système.', 'notifications_opt_own_page_changes' => 'Notifier lors des modifications des pages que je possède', 'notifications_opt_own_page_comments' => 'Notifier lorsque les pages que je possède sont commentées', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notifier les réponses à mes commentaires', 'notifications_save' => 'Enregistrer les préférences', 'notifications_update_success' => 'Les préférences de notification ont été mises à jour !', diff --git a/lang/fr/settings.php b/lang/fr/settings.php index e73d8a4b19d..7ce2312f3bf 100644 --- a/lang/fr/settings.php +++ b/lang/fr/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Aucune restriction en place', // Sorting Settings - 'sorting' => 'Tri', - 'sorting_book_default' => 'Tri des livres par défaut', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Sélectionnez le tri par défaut à mettre en place sur les nouveaux livres. Cela n’affectera pas les livres existants, et peut être redéfini dans les livres.', 'sorting_rules' => 'Règles de tri', 'sorting_rules_desc' => 'Ce sont les opérations de tri qui peuvent être appliquées au contenu du système.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Date de mise à jour', 'sort_rule_op_chapters_first' => 'Chapitres en premier', 'sort_rule_op_chapters_last' => 'Chapitres en dernier', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Maintenance', @@ -195,11 +197,13 @@ 'role_import_content' => 'Importer le contenu', 'role_editor_change' => 'Changer l\'éditeur de page', 'role_notifications' => 'Recevoir et gérer les notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Permissions des ressources', 'roles_system_warning' => 'Sachez que l\'accès à l\'une des trois permissions ci-dessus peut permettre à un utilisateur de modifier ses propres privilèges ou les privilèges des autres utilisateurs du système. N\'attribuez uniquement des rôles avec ces permissions qu\'à des utilisateurs de confiance.', 'role_asset_desc' => 'Ces permissions contrôlent l\'accès par défaut des ressources dans le système. Les permissions dans les livres, les chapitres et les pages ignoreront ces permissions', 'role_asset_admins' => 'Les administrateurs ont automatiquement accès à tous les contenus mais les options suivantes peuvent afficher ou masquer certaines options de l\'interface.', 'role_asset_image_view_note' => 'Cela concerne la visibilité dans le gestionnaire d\'images. L\'accès réel des fichiers d\'image téléchargés dépendra de l\'option de stockage d\'images du système.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Tous', 'role_own' => 'Propres', 'role_controlled_by_asset' => 'Contrôlé par les ressources les ayant envoyés', diff --git a/lang/he/notifications.php b/lang/he/notifications.php index 8385c0a6da3..cfab57117f4 100644 --- a/lang/he/notifications.php +++ b/lang/he/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'עמוד עודכן :pageName', 'updated_page_intro' => 'דף עודכן ב:appName:', 'updated_page_debounce' => 'על מנת לעצור הצפת התראות, לזמן מסוים אתה לא תקבל התראות על שינויים עתידיים בדף זה על ידי אותו עורך.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'שם עמוד:', 'detail_page_path' => 'נתיב לעמוד:', diff --git a/lang/he/preferences.php b/lang/he/preferences.php index 9e05eefb3ab..fa0c89ea585 100644 --- a/lang/he/preferences.php +++ b/lang/he/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'העדפות קבלת מייל והתראות כאשר מבוצעת פעולה מסויימת במערכת.', 'notifications_opt_own_page_changes' => 'עדכן אותי כאשר מתבצעים שינויים לדפים שבבעלותי', 'notifications_opt_own_page_comments' => 'עדכן אותי כאשר נוספות הערות לדפים שבבעלותי', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'עדכן אותי כאשר מתקבלות תגובות להערות שלי', 'notifications_save' => 'שמור העדפות', 'notifications_update_success' => 'הגדרת התראות עודכנו!', diff --git a/lang/he/settings.php b/lang/he/settings.php index 5b405072f86..0b5034475b9 100644 --- a/lang/he/settings.php +++ b/lang/he/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'אין הגבלה לדומיין', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'תחזוקה', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'שנה עורך עמודים', 'role_notifications' => 'ניהול התראות', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'הרשאות משאבים', 'roles_system_warning' => 'שימו לב לכך שגישה לכל אחת משלושת ההרשאות הנ"ל יכולה לאפשר למשתמש לשנות את הפריווילגיות שלהם או של אחרים במערכת. הגדירו תפקידים להרשאות אלה למשתמשים בהם אתם בוטחים בלבד.', 'role_asset_desc' => 'הרשאות אלו שולטות בגישת ברירת המחדל למשאבים בתוך המערכת. הרשאות של ספרים, פרקים ודפים יגברו על הרשאות אלו.', 'role_asset_admins' => 'מנהלים מקבלים הרשאה מלאה לכל התוכן אך אפשרויות אלו עלולות להציג או להסתיר אפשרויות בממשק', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'הכל', 'role_own' => 'שלי', 'role_controlled_by_asset' => 'נשלטים על ידי המשאב אליו הועלו', diff --git a/lang/hr/notifications.php b/lang/hr/notifications.php index c3a38cdfaa4..c9428e2dc58 100644 --- a/lang/hr/notifications.php +++ b/lang/hr/notifications.php @@ -13,6 +13,8 @@ Ažurirana stranica: :pageName', 'updated_page_intro' => 'Stranica je ažurirana u :appName:', 'updated_page_debounce' => 'Kako biste spriječili velik broj obavijesti, nećete primati obavijesti o daljnjim izmjenama ove stranice od istog urednika neko vrijeme.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Naziv Stranice:', 'detail_page_path' => 'Page Path:', diff --git a/lang/hr/preferences.php b/lang/hr/preferences.php index 2f409f8d3e4..5da5a72fb03 100644 --- a/lang/hr/preferences.php +++ b/lang/hr/preferences.php @@ -25,6 +25,7 @@ 'notifications_opt_own_page_comments' => 'ChatGPT Obavijesti o komentarima na stranicama koje posjedujem', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Obavijesti o odgovorima na moje komentare', 'notifications_save' => 'Spremi Postavke', 'notifications_update_success' => 'Postavke obavijesti su ažurirane!', diff --git a/lang/hr/settings.php b/lang/hr/settings.php index 5f000742631..6465d0ea72e 100644 --- a/lang/hr/settings.php +++ b/lang/hr/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Bez ograničenja', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Održavanje', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Promijeni uređivač stranica', 'role_notifications' => 'Primanje i upravljanje obavijestima', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Upravljanje vlasništvom', 'roles_system_warning' => 'Uzmite u obzir da pristup bilo kojem od ovih dopuštenja dozvoljavate korisniku upravljanje dopuštenjima ostalih u sustavu. Ova dopuštenja dodijelite pouzdanim korisnicima.', 'role_asset_desc' => 'Ova dopuštenja kontroliraju zadane pristupe. Dopuštenja za knjige, poglavlja i stranice ih poništavaju.', 'role_asset_admins' => 'Administratori automatski imaju pristup svim sadržajima, ali ove opcije mogu prikazati ili sakriti korisnička sučelja.', 'role_asset_image_view_note' => 'Ovo se odnosi na vidljivost unutar upravitelja slika. Stvarni pristup uploadiranim slikovnim datotekama ovisit će o odabranim opcijama pohrane slika u sustavu.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Sve', 'role_own' => 'Vlastito', 'role_controlled_by_asset' => 'Kontrolirano od strane vlasnika', diff --git a/lang/hu/notifications.php b/lang/hu/notifications.php index c8cae9143c3..d8a29688a13 100644 --- a/lang/hu/notifications.php +++ b/lang/hu/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Frissített oldal: :pageName', 'updated_page_intro' => 'Az oldal frissítése sikeres volt itt: :appName:', 'updated_page_debounce' => 'Az értesítések tömegének elkerülése érdekében egy ideig nem kap értesítést az oldal további szerkesztéseiről ugyanaz a szerkesztő.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Oldal neve:', 'detail_page_path' => 'Oldal helye:', diff --git a/lang/hu/preferences.php b/lang/hu/preferences.php index 4251a28fbc7..fb3c335e8ae 100644 --- a/lang/hu/preferences.php +++ b/lang/hu/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Állítsd be az e-mail értesítéseket, amelyeket akkor kapsz, ha bizonyos tevékenység történik a rendszeren belül.', 'notifications_opt_own_page_changes' => 'Értesítsen változásokról az általam tulajdonolt oldalakon', 'notifications_opt_own_page_comments' => 'Értesítés a hozzászólásokról az általam tulajdonolt oldalakon', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Értesítsen válaszokról a hozzászólásaimra', 'notifications_save' => 'Beállítások mentése', 'notifications_update_success' => 'Az értesítési beállítások frissítve lettek!', diff --git a/lang/hu/settings.php b/lang/hu/settings.php index 813fee7b054..c6810ed6838 100644 --- a/lang/hu/settings.php +++ b/lang/hu/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Nincs beállítva korlátozás', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Karbantartás', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Oldalszerkesztő módosítása', 'role_notifications' => 'Értesítések fogadása és kezelése', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Eszköz jogosultságok', 'roles_system_warning' => 'Ne feledje, hogy a fenti három engedély bármelyikéhez való hozzáférés lehetővé teszi a felhasználó számára, hogy módosítsa saját vagy a rendszerben mások jogosultságait. Csak megbízható felhasználókhoz rendeljen szerepeket ezekkel az engedélyekkel.', 'role_asset_desc' => 'Ezek a jogosultságok vezérlik az alapértelmezés szerinti hozzáférést a rendszerben található eszközökhöz. A könyvek, fejezetek és oldalak jogosultságai felülírják ezeket a jogosultságokat.', 'role_asset_admins' => 'Az adminisztrátorok automatikusan hozzáférést kapnak minden tartalomhoz, de ezek a beállítások megjeleníthetnek vagy elrejthetnek felhasználói felület beállításokat.', 'role_asset_image_view_note' => 'Ez a képkezelőn belüli láthatóságra vonatkozik. A feltöltött képfájlok tényleges elérése a rendszerkép tárolási beállításától függ.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Összes', 'role_own' => 'Saját', 'role_controlled_by_asset' => 'Az általuk feltöltött eszköz által ellenőrzött', diff --git a/lang/id/notifications.php b/lang/id/notifications.php index b22af1f730e..7b9d2181a25 100644 --- a/lang/id/notifications.php +++ b/lang/id/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Halaman yang diperbarui: :pageName', 'updated_page_intro' => 'Halaman telah diperbarui di :appName:', 'updated_page_debounce' => 'Untuk mencegah banyaknya pemberitahuan, untuk sementara Anda tidak akan dikirimi pemberitahuan untuk pengeditan lebih lanjut pada halaman ini oleh editor yang sama.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Nama Halaman:', 'detail_page_path' => 'Jalur Halaman:', diff --git a/lang/id/passwords.php b/lang/id/passwords.php index 3ee2e4d57da..2559b265133 100644 --- a/lang/id/passwords.php +++ b/lang/id/passwords.php @@ -6,7 +6,7 @@ */ return [ - 'password' => 'Kata sandi harus setidaknya delapan karakter dan sesuai dengan konfirmasi.', + 'password' => 'Passwords must be at least eight characters and match the confirmation.', 'user' => "Kami tidak dapat menemukan pengguna dengan alamat email tersebut.", 'token' => 'Token setel ulang sandi tidak valid untuk alamat email ini.', 'sent' => 'Kami telah mengirimkan email tautan pengaturan ulang kata sandi Anda!', diff --git a/lang/id/preferences.php b/lang/id/preferences.php index a1bb346b816..1c7d4248d6c 100644 --- a/lang/id/preferences.php +++ b/lang/id/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/id/settings.php b/lang/id/settings.php index 51c0613cbae..cc942668307 100644 --- a/lang/id/settings.php +++ b/lang/id/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Tidak ada batasan yang ditetapkan', // Sorting Settings - 'sorting' => 'Menyortir', - 'sorting_book_default' => 'Penyortiran Buku Default', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Pilih aturan sortir default yang akan diterapkan pada buku baru. Aturan ini tidak akan memengaruhi buku yang sudah ada, dan dapat diganti per buku.', 'sorting_rules' => 'Aturan Penyortiran', 'sorting_rules_desc' => 'Ini adalah operasi penyortiran yang telah ditetapkan sebelumnya yang dapat diterapkan pada konten dalam sistem.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Tanggal Pembaruan', 'sort_rule_op_chapters_first' => 'Bab di Urutan Pertama', 'sort_rule_op_chapters_last' => 'Bab di Urutan Terakhir', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Pemeliharaan', @@ -195,11 +197,13 @@ 'role_import_content' => 'Impor Konten', 'role_editor_change' => 'Ubah editor halaman', 'role_notifications' => 'Terima dan kelola notifikasi', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Izin Aset', 'roles_system_warning' => 'Ketahuilah bahwa akses ke salah satu dari tiga izin di atas dapat memungkinkan pengguna untuk mengubah hak mereka sendiri atau orang lain dalam sistem. Hanya tetapkan peran dengan izin ini untuk pengguna tepercaya.', 'role_asset_desc' => 'Izin ini mengontrol akses default ke aset dalam sistem. Izin pada Buku, Bab, dan Halaman akan menggantikan izin ini.', 'role_asset_admins' => 'Admin secara otomatis diberi akses ke semua konten tetapi opsi ini dapat menampilkan atau menyembunyikan opsi UI.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Semua', 'role_own' => 'Sendiri', 'role_controlled_by_asset' => 'Dikendalikan oleh aset tempat mereka diunggah', diff --git a/lang/is/notifications.php b/lang/is/notifications.php index b4fe01ad748..956fc743225 100644 --- a/lang/is/notifications.php +++ b/lang/is/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Uppfærð síða á: :pageName', 'updated_page_intro' => 'Síða hefur verið uppfærð á :appName:', 'updated_page_debounce' => 'Til að fyrirbyggja fjöldatilkynningar verður þér ekki sendar tilkynningar í smá stund um uppfærslu á þessari síðu frá sama höfundi.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Síðunafn:', 'detail_page_path' => 'Síðuslóð:', diff --git a/lang/is/preferences.php b/lang/is/preferences.php index b7ebc2df00a..b6f261deb46 100644 --- a/lang/is/preferences.php +++ b/lang/is/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Stýrðu þeim tölvupóst tilkynningum sem þú færð þegar ákveðnar aðgerðir eru gerðar af kerfinu.', 'notifications_opt_own_page_changes' => 'Láta vita þegar gerðar eru breytingar á síðum sem ég á', 'notifications_opt_own_page_comments' => 'Láta vita þegar gerðar eru athugasmedir við síður sem ég á', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Láta vita þegar athugasemdum mínum er svarað', 'notifications_save' => 'Vista stillingar', 'notifications_update_success' => 'Stillingar á tilkynningum hafa verið uppfærðar!', diff --git a/lang/is/settings.php b/lang/is/settings.php index 3765db061cc..5699c88d623 100644 --- a/lang/is/settings.php +++ b/lang/is/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Engin skilyrði sett', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Viðhald', @@ -195,11 +197,13 @@ 'role_import_content' => 'Flytja inn efni', 'role_editor_change' => 'Skipta um ritil síðu', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Asset Permissions', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', 'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Allt', 'role_own' => 'Eigin', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', diff --git a/lang/it/notifications.php b/lang/it/notifications.php index 6b8932ebee0..a4e57abdf65 100644 --- a/lang/it/notifications.php +++ b/lang/it/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Pagina aggiornata: :pageName', 'updated_page_intro' => 'Una pagina è stata aggiornata in :appName:', 'updated_page_debounce' => 'Per evitare una massa di notifiche, per un po\' non ti verranno inviate notifiche per ulteriori modifiche a questa pagina dallo stesso editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Nome della pagina:', 'detail_page_path' => 'Percorso della pagina:', diff --git a/lang/it/preferences.php b/lang/it/preferences.php index f251384011b..db6b907541d 100644 --- a/lang/it/preferences.php +++ b/lang/it/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Controlla le notifiche email che ricevi quando viene eseguita una determinata attività all\'interno del sistema.', 'notifications_opt_own_page_changes' => 'Notifica in caso di modifiche alle pagine che possiedo', 'notifications_opt_own_page_comments' => 'Notifica i commenti sulle pagine che possiedo', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notificare le risposte ai miei commenti', 'notifications_save' => 'Salva preferenze', 'notifications_update_success' => 'Le preferenze di notifica sono state aggiornate!', diff --git a/lang/it/settings.php b/lang/it/settings.php index 8bda7f8ffe9..9f2d882af40 100644 --- a/lang/it/settings.php +++ b/lang/it/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Nessuna restrizione impostata', // Sorting Settings - 'sorting' => 'Ordinamento', - 'sorting_book_default' => 'Ordinamento libri predefinito', + 'sorting' => 'Elenchi E Ordinamento', + 'sorting_book_default' => 'Regola Di Ordinamento Libro Predefinita', 'sorting_book_default_desc' => 'Selezionare la regola di ordinamento predefinita da applicare ai nuovi libri. Questa regola non influisce sui libri esistenti e può essere modificata per ogni libro.', 'sorting_rules' => 'Regole di ordinamento', 'sorting_rules_desc' => 'Si tratta di operazioni di ordinamento predefinite applicabili ai contenuti del sistema.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Data di aggiornamento', 'sort_rule_op_chapters_first' => 'Capitoli Prima', 'sort_rule_op_chapters_last' => 'Capitoli dopo', + 'sorting_page_limits' => 'Limiti Visualizzazione Per Pagina', + 'sorting_page_limits_desc' => 'Imposta il numero di elementi da visualizzare per pagina nei vari elenchi all\'interno del sistema. In genere, un numero inferiore garantisce prestazioni migliori, mentre un numero più elevato evita la necessità di cliccare su più pagine. Si consiglia di utilizzare un multiplo pari di 3 (18, 24, 30, ecc...).', // Maintenance settings 'maint' => 'Manutenzione', @@ -195,11 +197,13 @@ 'role_import_content' => 'Importa contenuto', 'role_editor_change' => 'Cambiare editor di pagina', 'role_notifications' => 'Ricevere e gestire le notifiche', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Permessi entità', 'roles_system_warning' => 'Siate consapevoli che l\'accesso a uno dei tre permessi qui sopra può consentire a un utente di modificare i propri privilegi o i privilegi di altri nel sistema. Assegna ruoli con questi permessi solo ad utenti fidati.', 'role_asset_desc' => 'Questi permessi controllano l\'accesso predefinito alle entità. I permessi in libri, capitoli e pagine sovrascriveranno questi.', 'role_asset_admins' => 'Gli amministratori hanno automaticamente accesso a tutti i contenuti ma queste opzioni possono mostrare o nascondere le opzioni della UI.', 'role_asset_image_view_note' => 'Questo si riferisce alla visibilità all\'interno del gestore delle immagini. L\'accesso effettivo ai file di immagine caricati dipenderà dall\'opzione di archiviazione delle immagini di sistema.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Tutti', 'role_own' => 'Propri', 'role_controlled_by_asset' => 'Controllato dall\'entità in cui sono caricati', diff --git a/lang/ja/notifications.php b/lang/ja/notifications.php index 6fc0321d353..98193217898 100644 --- a/lang/ja/notifications.php +++ b/lang/ja/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'ページの更新: :pageName', 'updated_page_intro' => ':appName でページが更新されました', 'updated_page_debounce' => '大量の通知を防ぐために、しばらくの間は同じユーザがこのページをさらに編集しても通知は送信されません。', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'ページ名:', 'detail_page_path' => 'ページパス:', diff --git a/lang/ja/preferences.php b/lang/ja/preferences.php index 0207596a9ec..a4790692953 100644 --- a/lang/ja/preferences.php +++ b/lang/ja/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'システム内で特定のアクティビティが実行されたときに受信する電子メール通知を制御します。', 'notifications_opt_own_page_changes' => '自分が所有するページの変更を通知する', 'notifications_opt_own_page_comments' => '自分が所有するページへのコメントを通知する', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => '自分のコメントへの返信を通知する', 'notifications_save' => '設定を保存', 'notifications_update_success' => '通知設定を更新しました。', diff --git a/lang/ja/settings.php b/lang/ja/settings.php index db14f3ccc5a..a19c942b26d 100644 --- a/lang/ja/settings.php +++ b/lang/ja/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => '制限しない', // Sorting Settings - 'sorting' => 'ソート', - 'sorting_book_default' => 'ブックのデフォルトソート', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => '新しいブックに適用するデフォルトのソートルールを選択します。これは既存のブックには影響しません。ルールはブックごとに上書きすることができます。', 'sorting_rules' => 'ソートルール', 'sorting_rules_desc' => 'これらはシステム内のコンテンツに適用できる事前定義のソート操作です。', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => '更新日時', 'sort_rule_op_chapters_first' => 'チャプタを最初に', 'sort_rule_op_chapters_last' => 'チャプタを最後に', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'メンテナンス', @@ -195,11 +197,13 @@ 'role_import_content' => 'コンテンツのインポート', 'role_editor_change' => 'ページエディタの変更', 'role_notifications' => '通知の受信と管理', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'アセット権限', 'roles_system_warning' => '上記の3つの権限のいずれかを付与することは、ユーザーが自分の特権またはシステム内の他のユーザーの特権を変更できる可能性があることに注意してください。これらの権限は信頼できるユーザーにのみ割り当ててください。', 'role_asset_desc' => '各アセットに対するデフォルトの権限を設定します。ここで設定した権限が優先されます。', 'role_asset_admins' => '管理者にはすべてのコンテンツへのアクセス権が自動的に付与されますが、これらのオプションはUIオプションを表示または非表示にする場合があります。', 'role_asset_image_view_note' => 'これは画像マネージャー内の可視性に関連しています。アップロードされた画像ファイルへの実際のアクセスは、システムの画像保存オプションに依存します。', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => '全て', 'role_own' => '自身', 'role_controlled_by_asset' => 'このアセットに対し、右記の操作を許可:', diff --git a/lang/ka/notifications.php b/lang/ka/notifications.php index 1afd23f1dc4..563ac24e84d 100644 --- a/lang/ka/notifications.php +++ b/lang/ka/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Updated page: :pageName', 'updated_page_intro' => 'A page has been updated in :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Page Name:', 'detail_page_path' => 'Page Path:', diff --git a/lang/ka/preferences.php b/lang/ka/preferences.php index 2872f5f3c65..f4459d738e4 100644 --- a/lang/ka/preferences.php +++ b/lang/ka/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/ka/settings.php b/lang/ka/settings.php index 81c2c0a93c3..c68605fe1f8 100644 --- a/lang/ka/settings.php +++ b/lang/ka/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'No restriction set', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Maintenance', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Change page editor', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Asset Permissions', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', 'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'All', 'role_own' => 'Own', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', diff --git a/lang/ko/notifications.php b/lang/ko/notifications.php index b337fa86de2..a5abcf0f9ff 100644 --- a/lang/ko/notifications.php +++ b/lang/ko/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => '페이지 업데이트됨: :pageName', 'updated_page_intro' => ':appName: 에서 페이지가 업데이트되었습니다:', 'updated_page_debounce' => '알림이 한꺼번에 몰리는 것을 방지하기 위해 당분간 동일한 편집자가 이 페이지를 추가로 편집할 경우 알림이 전송되지 않습니다.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => '페이지 이름:', 'detail_page_path' => '페이지 경로:', diff --git a/lang/ko/preferences.php b/lang/ko/preferences.php index 79692f564c3..1979a23e4b1 100644 --- a/lang/ko/preferences.php +++ b/lang/ko/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => '시스템 내에서 특정 활동이 수행될 때 수신하는 이메일 알림을 제어합니다.', 'notifications_opt_own_page_changes' => '내가 소유한 페이지가 변경되면 알림 받기', 'notifications_opt_own_page_comments' => '내가 소유한 페이지에 댓글이 달렸을 때 알림 받기', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => '내 댓글에 대한 답글 알림 받기', 'notifications_save' => '환경설정 저장', 'notifications_update_success' => '알림 환경설정이 업데이트되었습니다!', diff --git a/lang/ko/settings.php b/lang/ko/settings.php index 797c12043a1..1138b0ece8e 100644 --- a/lang/ko/settings.php +++ b/lang/ko/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => '차단한 도메인 없음', // Sorting Settings - 'sorting' => '정렬', - 'sorting_book_default' => '기본 책 정렬', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => '새로운 책에 적용할 기본 정렬 규칙을 선택하세요. 이 선택은 기존 책에는 영향을 주지 않고, 기존 책의 설정은 책마다 변경할 수 있습니다.', 'sorting_rules' => '정렬 규칙', 'sorting_rules_desc' => '현재 시스템에 미리 정의된 정렬 규칙의 목록입니다.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => '수정일', 'sort_rule_op_chapters_first' => '챕터 우선 정렬', 'sort_rule_op_chapters_last' => '챕터 나중 정렬', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => '유지관리', @@ -195,11 +197,13 @@ 'role_import_content' => '내용 가져오기', 'role_editor_change' => '페이지 편집기 변경', 'role_notifications' => '알림 수신 및 관리', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => '권한 항목', 'roles_system_warning' => '위 세 권한은 자신의 권한이나 다른 유저의 권한을 바꿀 수 있습니다.', 'role_asset_desc' => '책, 챕터, 문서별 권한은 이 설정에 우선합니다.', 'role_asset_admins' => '관리자 권한은 어디든 접근할 수 있지만 이 설정은 사용자 인터페이스에서 해당 활동을 표시할지 결정합니다.', 'role_asset_image_view_note' => '이는 이미지 관리자 내 가시성과 관련이 있습니다. 업로드된 이미지 파일의 실제 접근은 시스템의 이미지 저장 설정에 따라 달라집니다.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => '모든 항목', 'role_own' => '직접 만든 항목', 'role_controlled_by_asset' => '저마다 다름', diff --git a/lang/ku/notifications.php b/lang/ku/notifications.php index 1afd23f1dc4..563ac24e84d 100644 --- a/lang/ku/notifications.php +++ b/lang/ku/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Updated page: :pageName', 'updated_page_intro' => 'A page has been updated in :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Page Name:', 'detail_page_path' => 'Page Path:', diff --git a/lang/ku/preferences.php b/lang/ku/preferences.php index 2872f5f3c65..f4459d738e4 100644 --- a/lang/ku/preferences.php +++ b/lang/ku/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/ku/settings.php b/lang/ku/settings.php index 81c2c0a93c3..c68605fe1f8 100644 --- a/lang/ku/settings.php +++ b/lang/ku/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'No restriction set', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Maintenance', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Change page editor', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Asset Permissions', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', 'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'All', 'role_own' => 'Own', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', diff --git a/lang/lt/notifications.php b/lang/lt/notifications.php index 1afd23f1dc4..563ac24e84d 100644 --- a/lang/lt/notifications.php +++ b/lang/lt/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Updated page: :pageName', 'updated_page_intro' => 'A page has been updated in :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Page Name:', 'detail_page_path' => 'Page Path:', diff --git a/lang/lt/preferences.php b/lang/lt/preferences.php index 5c38d191c0b..6258fcae754 100644 --- a/lang/lt/preferences.php +++ b/lang/lt/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/lt/settings.php b/lang/lt/settings.php index 563038bf267..8120d85fe8e 100644 --- a/lang/lt/settings.php +++ b/lang/lt/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Nėra jokių apribojimų', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Priežiūra', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Change page editor', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Nuosavybės leidimai', 'roles_system_warning' => 'Būkite sąmoningi, kad prieiga prie bet kurio iš trijų leidimų viršuje gali leisti naudotojui pakeisti jų pačių privilegijas arba kitų privilegijas sistemoje. Paskirkite vaidmenis su šiais leidimais tik patikimiems naudotojams.', 'role_asset_desc' => 'Šie leidimai kontroliuoja numatytą prieigą į nuosavybę, esančią sistemoje. Knygų, skyrių ir puslapių leidimai nepaisys šių leidimų.', 'role_asset_admins' => 'Administratoriams automatiškai yra suteikiama prieiga prie viso turinio, tačiau šie pasirinkimai gali rodyti arba slėpti vartotojo sąsajos parinktis.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Visi', 'role_own' => 'Nuosavi', 'role_controlled_by_asset' => 'Kontroliuojami nuosavybės, į kurią yra įkelti', diff --git a/lang/lv/notifications.php b/lang/lv/notifications.php index 9fd60222a72..7233822a46f 100644 --- a/lang/lv/notifications.php +++ b/lang/lv/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Atjaunināta lapa: :pageName', 'updated_page_intro' => 'Lapa atjaunināta :appName:', 'updated_page_debounce' => 'Lai novērstu pārliecīgu paziņojumu sūtīšanu, uz laiku jums tiks pārtraukti paziņojumi par turpmākiem šī lietotāja labojumiem šai lapai.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Lapas nosaukums:', 'detail_page_path' => 'Ceļš uz lapu:', diff --git a/lang/lv/preferences.php b/lang/lv/preferences.php index b91cc2e0833..3c107d5fa01 100644 --- a/lang/lv/preferences.php +++ b/lang/lv/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Pārvaldiet epasta paziņojumus, ko saņemsiet, kad sistēmā tiek veiktas noteiktas darbības.', 'notifications_opt_own_page_changes' => 'Paziņot par izmaiņām manās lapās', 'notifications_opt_own_page_comments' => 'Paziņot par komentāriem manās lapās', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Paziņot par atbildēm uz maniem komentāriem', 'notifications_save' => 'Saglabāt iestatījumus', 'notifications_update_success' => 'Paziņojumu iestatījumi ir atjaunoti!', diff --git a/lang/lv/settings.php b/lang/lv/settings.php index 9901f781190..fb5cf13ddee 100644 --- a/lang/lv/settings.php +++ b/lang/lv/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Nav ierobežojumu', // Sorting Settings - 'sorting' => 'Kārtošana', - 'sorting_book_default' => 'Noklusētā grāmatu kārtošana', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Izvēlieties noklusēto kārtošanas nosacījumu, ko pielietot jaunām grāmatām. Šis neskars jau esošas grāmatas, un to var izmainīt grāmatas iestatījumos.', 'sorting_rules' => 'Kārtošanas noteikumi', 'sorting_rules_desc' => 'Šīs ir iepriekš noteiktas kārtošanas darbības, ko var pielietot saturam šajā sistēmā.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Atjaunināšanas datums', 'sort_rule_op_chapters_first' => 'Nodaļas pirmās', 'sort_rule_op_chapters_last' => 'Nodaļas pēdējās', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Apkope', @@ -195,11 +197,13 @@ 'role_import_content' => 'Importēt saturu', 'role_editor_change' => 'Mainīt lapu redaktoru', 'role_notifications' => 'Saņemt un pārvaldīt paziņojumus', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Resursa piekļuves tiesības', 'roles_system_warning' => 'Jebkuras no trīs augstāk redzamajām atļaujām dod iespēju lietotājam mainīt savas un citu lietotāju sistēmas atļaujas. Pievieno šīs grupu atļaujas tikai tiem lietotājiem, kuriem uzticies.', 'role_asset_desc' => 'Šīs piekļuves tiesības kontrolē noklusēto piekļuvi sistēmas resursiem. Grāmatām, nodaļām un lapām norādītās tiesības būs pārākas par šīm.', 'role_asset_admins' => 'Administratoriem automātiski ir piekļuve visam saturam, bet šie uzstādījumi var noslēpt vai parādīt lietotāja saskarnes iespējas.', 'role_asset_image_view_note' => 'Šis ir saistīts ar redzamību attēlu pārvaldniekā. Faktiskā piekļuve augšupielādēto attēlu failiem būs atkarīga no sistēmas attēlu glabātuves uzstādījuma.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Visi', 'role_own' => 'Savi', 'role_controlled_by_asset' => 'Kontrolē resurss, uz ko tie ir augšupielādēti', diff --git a/lang/nb/editor.php b/lang/nb/editor.php index 77b01f17ba9..23a788d71ee 100644 --- a/lang/nb/editor.php +++ b/lang/nb/editor.php @@ -48,7 +48,7 @@ 'superscript' => 'Hevet skrift', 'subscript' => 'Senket skrift', 'text_color' => 'Tekstfarge', - 'highlight_color' => 'Highlight color', + 'highlight_color' => 'Uthevingsfarge', 'custom_color' => 'Egenvalgt farge', 'remove_color' => 'Fjern farge', 'background_color' => 'Bakgrunnsfarge', diff --git a/lang/nb/notifications.php b/lang/nb/notifications.php index bb46e09dbe8..1e16229f31f 100644 --- a/lang/nb/notifications.php +++ b/lang/nb/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Oppdatert side: :pageName', 'updated_page_intro' => 'En side er oppdatert i :appName:', 'updated_page_debounce' => 'For å forhindre mange varslinger, vil du ikke få nye varslinger for endringer på denne siden fra samme forfatter.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Sidenavn:', 'detail_page_path' => 'Side bane:', diff --git a/lang/nb/preferences.php b/lang/nb/preferences.php index 245c9c954f1..6a6850b8371 100644 --- a/lang/nb/preferences.php +++ b/lang/nb/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Kontroller e-postvarslene du mottar når en bestemt aktivitet utføres i systemet.', 'notifications_opt_own_page_changes' => 'Varsle ved endringer til sider jeg eier', 'notifications_opt_own_page_comments' => 'Varsle om kommentarer på sider jeg eier', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Varsle ved svar på mine kommentarer', 'notifications_save' => 'Lagre innstillinger', 'notifications_update_success' => 'Varslingsinnstillingene er oppdatert!', diff --git a/lang/nb/settings.php b/lang/nb/settings.php index 68bab6f3038..d1f40814edf 100644 --- a/lang/nb/settings.php +++ b/lang/nb/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Ingen begrensninger er satt', // Sorting Settings - 'sorting' => 'Sortering', - 'sorting_book_default' => 'Standard boksortering', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Velg standard sorteringsregelen som skal brukes for nye bøker. Dette vil ikke påvirke eksisterende bøker, og kan overstyres per bok.', 'sorting_rules' => 'Sorteringsregler', 'sorting_rules_desc' => 'Dette er forhåndsdefinerte sorteringsoperasjoner som kan brukes på innhold i systemet.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Kapitler først', 'sort_rule_op_chapters_last' => 'Kapitler sist', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Vedlikehold', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import innhold', 'role_editor_change' => 'Endre sideredigering', 'role_notifications' => 'Motta og administrere varslinger', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Eiendomstillatelser', 'roles_system_warning' => 'Vær oppmerksom på at tilgang til noen av de ovennevnte tre tillatelsene kan tillate en bruker å endre sine egne rettigheter eller rettighetene til andre i systemet. Bare tildel roller med disse tillatelsene til pålitelige brukere.', 'role_asset_desc' => 'Disse tillatelsene kontrollerer standard tilgang til eiendelene i systemet. Tillatelser til bøker, kapitler og sider overstyrer disse tillatelsene.', 'role_asset_admins' => 'Administratorer får automatisk tilgang til alt innhold, men disse alternativene kan vise eller skjule UI-alternativer.', 'role_asset_image_view_note' => 'Dette gjelder synlighet innenfor bilde-administrasjonen. Faktisk tilgang på opplastede bildefiler vil være avhengig av valget for systemlagring av bildet.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Alle', 'role_own' => 'Egne', 'role_controlled_by_asset' => 'Kontrollert av eiendelen de er lastet opp til', diff --git a/lang/ne/notifications.php b/lang/ne/notifications.php index 1e9c572215a..a4eb6983262 100644 --- a/lang/ne/notifications.php +++ b/lang/ne/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'पाना अपडेट भयो: :pageName', 'updated_page_intro' => ':appName मा पाना अपडेट गरिएको छ', 'updated_page_debounce' => 'धेरै सूचना नपरोस् भनेर, केही समयको लागि एउटै सम्पादकबाट हुने थप सम्पादनहरूका सूचना तपाईंलाई पठाइने छैन।', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'पानाको नाम:', 'detail_page_path' => 'पानाको स्थान:', diff --git a/lang/ne/preferences.php b/lang/ne/preferences.php index 70388e8baef..2efdafc9f5c 100644 --- a/lang/ne/preferences.php +++ b/lang/ne/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'प्रणालीमा केही क्रियाकलापहरू गर्दा तपाईंलाई प्राप्त हुने इमेल सूचनाहरू नियन्त्रण गर्नुहोस्।', 'notifications_opt_own_page_changes' => 'मैले स्वामित्व राख्ने पृष्ठहरूमा परिवर्तन हुँदा सूचित गर्नुहोस्', 'notifications_opt_own_page_comments' => 'मैले स्वामित्व राख्ने पृष्ठहरूमा टिप्पणी हुँदा सूचित गर्नुहोस्', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'मेरो टिप्पणीहरूमा उत्तर आएको बेला सूचित गर्नुहोस्', 'notifications_save' => 'प्राथमिकताहरू बचत गर्नुहोस्', 'notifications_update_success' => 'सूचना प्राथमिकताहरू अपडेट गरिएका छन्!', diff --git a/lang/ne/settings.php b/lang/ne/settings.php index eb99f26bdf7..37e59978e1f 100644 --- a/lang/ne/settings.php +++ b/lang/ne/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'कुनै प्रतिबन्ध छैन', // Sorting Settings - 'sorting' => 'क्रमबद्धता', - 'sorting_book_default' => 'डिफल्ट पुस्तक क्रम', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'नयाँ पुस्तकहरूमा लागु गर्न डिफल्ट क्रम नियम चयन गर्नुहोस्। यो अस्तित्वमा रहेका पुस्तकहरूमा असर पार्दैन र पुस्तक अनुसार ओभरराइड गर्न सकिन्छ।', 'sorting_rules' => 'क्रम नियमहरू', 'sorting_rules_desc' => 'यी पूर्वनिर्धारित क्रम सञ्चालनहरू हुन् जुन प्रणालीमा सामग्रीमा लागू गर्न सकिन्छ।', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'अपडेट मिति', 'sort_rule_op_chapters_first' => 'पहिले अध्यायहरू', 'sort_rule_op_chapters_last' => 'अन्त्यमा अध्यायहरू', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'सम्भार', @@ -195,11 +197,13 @@ 'role_import_content' => 'सामग्री आयात गर्नुहोस्', 'role_editor_change' => 'पृष्ठ सम्पादक परिवर्तन गर्नुहोस्', 'role_notifications' => 'सूचनाहरू प्राप्त र व्यवस्थापन गर्नुहोस्', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'संपत्ति अनुमति', 'roles_system_warning' => 'माथिका कुनै पनि तीन अनुमति प्रयोगकर्ताले आफैं वा अरूका अधिकार परिवर्तन गर्न सक्छन्। यी अनुमति भएको भूमिका मात्र भरपर्दो प्रयोगकर्तालाई दिनुहोस्।', 'role_asset_desc' => 'यी अनुमतिले प्रणालीभित्र सम्पत्तिमा डिफल्ट पहुँच नियन्त्रण गर्छ। पुस्तक, अध्याय र पृष्ठमा अनुमति यी भन्दा प्राथमिक हुन्छ।', 'role_asset_admins' => 'प्रशासनकर्ताहरूलाई सबै सामग्रीमा स्वतः पहुँच दिइन्छ, यी विकल्पहरूले UI मा देखिने वा लुकेका विकल्पहरू मात्र प्रभाव पार्न सक्छ।', 'role_asset_image_view_note' => 'यो छवि व्यवस्थापक भित्रको दृश्यता सम्बन्धि हो। अपलोड गरिएको छविमा वास्तविक पहुँच प्रणालीको छवि भण्डारण विकल्प अनुसार हुन्छ।', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'सबै', 'role_own' => 'आफ्नो', 'role_controlled_by_asset' => 'अपलोड गरिएको सम्पत्तिले नियन्त्रण गरेको', diff --git a/lang/nl/notifications.php b/lang/nl/notifications.php index 1e7035670da..ce2a3ebff02 100644 --- a/lang/nl/notifications.php +++ b/lang/nl/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Aangepaste pagina: :pageName', 'updated_page_intro' => 'Een pagina werd aangepast in :appName:', 'updated_page_debounce' => 'Om een stortvloed aan meldingen te voorkomen, zul je een tijdje geen meldingen ontvangen voor verdere bewerkingen van deze pagina door dezelfde redacteur.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Pagina Naam:', 'detail_page_path' => 'Paginapad:', diff --git a/lang/nl/preferences.php b/lang/nl/preferences.php index d0e3623b9cc..1fb56af074e 100644 --- a/lang/nl/preferences.php +++ b/lang/nl/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Bepaal welke e-mailmeldingen je ontvangt wanneer bepaalde activiteiten in het systeem worden uitgevoerd.', 'notifications_opt_own_page_changes' => 'Geef melding bij wijzigingen aan pagina\'s waarvan ik de eigenaar ben', 'notifications_opt_own_page_comments' => 'Geef melding van opmerkingen op pagina\'s waarvan ik de eigenaar ben', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Geef melding van reacties op mijn opmerkingen', 'notifications_save' => 'Voorkeuren opslaan', 'notifications_update_success' => 'Voorkeuren voor meldingen zijn bijgewerkt!', diff --git a/lang/nl/settings.php b/lang/nl/settings.php index 3b4da75a985..2041669eb12 100644 --- a/lang/nl/settings.php +++ b/lang/nl/settings.php @@ -75,7 +75,7 @@ 'reg_confirm_restrict_domain_placeholder' => 'Geen beperkingen ingesteld', // Sorting Settings - 'sorting' => 'Sorteren', + 'sorting' => 'Lijsten & Sorteren', 'sorting_book_default' => 'Standaard Sorteerregel Boek', 'sorting_book_default_desc' => 'Selecteer de standaard sorteerregel om toe te passen op nieuwe boeken. Dit heeft geen invloed op bestaande boeken, en kan per boek worden overschreven.', 'sorting_rules' => 'Sorteerregels', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Bijwerkdatum', 'sort_rule_op_chapters_first' => 'Hoofdstukken Eerst', 'sort_rule_op_chapters_last' => 'Hoofdstukken Laatst', + 'sorting_page_limits' => 'Weergavelimiet Per Pagina', + 'sorting_page_limits_desc' => 'Stel in hoeveel items er op een pagina worden laten zien in de verschillende lijstweergaves. Een lager aantal verbeterd de snelheid, een hoger aantal verminderd het doorklikken door pagina\'s. Een even veelvoud van 3 (18, 24, 30, etc...) wordt aanbevolen.', // Maintenance settings 'maint' => 'Onderhoud', @@ -195,11 +197,13 @@ 'role_import_content' => 'Importeer inhoud', 'role_editor_change' => 'Wijzig pagina bewerker', 'role_notifications' => 'Meldingen ontvangen & beheren', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Asset Machtigingen', 'roles_system_warning' => 'Wees ervan bewust dat toegang tot een van de bovengenoemde drie machtigingen een gebruiker in staat kan stellen zijn eigen machtigingen of de machtigingen van anderen in het systeem kan wijzigen. Wijs alleen rollen toe met deze machtigingen aan vertrouwde gebruikers.', 'role_asset_desc' => 'Deze machtigingen bepalen de standaard toegang tot de assets binnen het systeem. Machtigingen op boeken, hoofdstukken en pagina\'s overschrijven deze instelling.', 'role_asset_admins' => 'Beheerders krijgen automatisch toegang tot alle inhoud, maar deze opties kunnen gebruikersinterface opties tonen of verbergen.', 'role_asset_image_view_note' => 'Dit heeft betrekking op de zichtbaarheid binnen de afbeeldingsbeheerder. De werkelijke toegang tot geüploade afbeeldingsbestanden hangt af van de gekozen opslagmethode.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Alles', 'role_own' => 'Eigen', 'role_controlled_by_asset' => 'Gecontroleerd door de asset waar deze is geüpload', diff --git a/lang/nn/notifications.php b/lang/nn/notifications.php index 247d8d10572..25f0f30c86d 100644 --- a/lang/nn/notifications.php +++ b/lang/nn/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Oppdatert side: :pageName', 'updated_page_intro' => 'Ei side vart oppdatert i :appName:', 'updated_page_debounce' => 'For å forhindre mange varslingar, vil du ikkje få nye varslinger for endringar på denne siden frå same forfattar.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Sidenamn:', 'detail_page_path' => 'Sidenamn:', diff --git a/lang/nn/preferences.php b/lang/nn/preferences.php index ac6dc1b77dc..17d1fca4257 100644 --- a/lang/nn/preferences.php +++ b/lang/nn/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Kontroller e-postvarslene du mottar når en bestemt aktivitet utføres i systemet.', 'notifications_opt_own_page_changes' => 'Varsle ved endringer til sider jeg eier', 'notifications_opt_own_page_comments' => 'Varsle om kommentarer på sider jeg eier', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Varsle ved svar på mine kommentarer', 'notifications_save' => 'Lagre innstillinger', 'notifications_update_success' => 'Varslingsinnstillingene er oppdatert!', diff --git a/lang/nn/settings.php b/lang/nn/settings.php index c098ac75386..6d2259bd88d 100644 --- a/lang/nn/settings.php +++ b/lang/nn/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Ingen begrensninger er satt', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Vedlikehold', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Endre sideredigering', 'role_notifications' => 'Motta og administrere varslinger', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Eiendomstillatelser', 'roles_system_warning' => 'Vær oppmerksom på at tilgang til noen av de ovennevnte tre tillatelsene kan tillate en bruker å endre sine egne rettigheter eller rettighetene til andre i systemet. Bare tildel roller med disse tillatelsene til pålitelige brukere.', 'role_asset_desc' => 'Disse tillatelsene kontrollerer standard tilgang til eiendelene i systemet. Tillatelser til bøker, kapitler og sider overstyrer disse tillatelsene.', 'role_asset_admins' => 'Administratorer får automatisk tilgang til alt innhold, men disse alternativene kan vise eller skjule UI-alternativer.', 'role_asset_image_view_note' => 'Dette gjelder synlighet innenfor bilde-administrasjonen. Faktisk tilgang på opplastede bildefiler vil være avhengig av valget for systemlagring av bildet.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Alle', 'role_own' => 'Egne', 'role_controlled_by_asset' => 'Kontrollert av eiendelen de er lastet opp til', diff --git a/lang/pl/notifications.php b/lang/pl/notifications.php index 66114999446..a2c9ff1c0b9 100644 --- a/lang/pl/notifications.php +++ b/lang/pl/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Zaktualizowano stronę: :pageName', 'updated_page_intro' => 'Strona została zaktualizowana w :appName:', 'updated_page_debounce' => 'Aby zapobiec nadmiarowi powiadomień, przez jakiś czas nie będziesz otrzymywać powiadomień o dalszych edycjach tej strony przez tego samego edytora.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Nazwa strony:', 'detail_page_path' => 'Ścieżka strony:', diff --git a/lang/pl/preferences.php b/lang/pl/preferences.php index dd348210279..372b8eda6df 100644 --- a/lang/pl/preferences.php +++ b/lang/pl/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Kontroluj otrzymywane powiadomienia e-mail, gdy określona aktywność jest wykonywana w systemie.', 'notifications_opt_own_page_changes' => 'Powiadom o zmianach na stronach, których jestem właścicielem', 'notifications_opt_own_page_comments' => 'Powiadom o komentarzach na stronach, których jestem właścicielem', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Powiadom o odpowiedziach na moje komentarze', 'notifications_save' => 'Zapisz preferencje', 'notifications_update_success' => 'Preferencje powiadomień zostały zaktualizowane!', diff --git a/lang/pl/settings.php b/lang/pl/settings.php index 7c84ce34b87..c32797f2f53 100644 --- a/lang/pl/settings.php +++ b/lang/pl/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Brak restrykcji', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Konserwacja', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Zmień edytor strony', 'role_notifications' => 'Odbieranie i zarządzanie powiadomieniami', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Zarządzanie zasobami', 'roles_system_warning' => 'Pamiętaj, że dostęp do trzech powyższych uprawnień może pozwolić użytkownikowi na zmianę własnych uprawnień lub uprawnień innych osób w systemie. Przypisz tylko role z tymi uprawnieniami do zaufanych użytkowników.', 'role_asset_desc' => 'Te ustawienia kontrolują zarządzanie zasobami systemu. Uprawnienia książek, rozdziałów i stron nadpisują te ustawienia.', 'role_asset_admins' => 'Administratorzy mają automatycznie dostęp do wszystkich treści, ale te opcję mogą być pokazywać lub ukrywać opcje interfejsu użytkownika.', 'role_asset_image_view_note' => 'To odnosi się do widoczności w ramach menedżera obrazów. Rzeczywista możliwość dostępu do przesłanych plików obrazów będzie zależeć od systemowej opcji przechowywania obrazów.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Wszyscy', 'role_own' => 'Własne', 'role_controlled_by_asset' => 'Kontrolowane przez zasób, do którego zostały udostępnione', diff --git a/lang/pt/notifications.php b/lang/pt/notifications.php index 1243c6680f4..cbe3a511c88 100644 --- a/lang/pt/notifications.php +++ b/lang/pt/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Página atualizada: :pageName', 'updated_page_intro' => 'Uma página foi atualizada em :appName:', 'updated_page_debounce' => 'Para evitar um grande volume de notificações, durante algum tempo não serão enviadas notificações de edições futuras para esta página através do mesmo editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Nome da Página:', 'detail_page_path' => 'Page Path:', diff --git a/lang/pt/preferences.php b/lang/pt/preferences.php index 860eec645bc..b7308aaf910 100644 --- a/lang/pt/preferences.php +++ b/lang/pt/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Controlar as notificações via correio eletrónico quando certas atividades são executadas pelo sistema.', 'notifications_opt_own_page_changes' => 'Notificar quando páginas que possuo sofrem alterações', 'notifications_opt_own_page_comments' => 'Notificar quando comentam páginas que possuo', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notificar respostas aos meus comentários', 'notifications_save' => 'Guardar preferências', 'notifications_update_success' => 'Preferências de notificação foram atualizadas!', diff --git a/lang/pt/settings.php b/lang/pt/settings.php index 0c765fb9851..c2179e64027 100644 --- a/lang/pt/settings.php +++ b/lang/pt/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Nenhuma restrição definida', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Manutenção', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Alterar editor de página', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Permissões de Ativos', 'roles_system_warning' => 'Esteja ciente de que o acesso a qualquer uma das três permissões acima pode permitir que um utilizador altere os seus próprios privilégios ou privilégios de outros no sistema. Apenas atribua cargos com essas permissões a utilizadores de confiança.', 'role_asset_desc' => 'Estas permissões controlam o acesso padrão para os ativos dentro do sistema. Permissões em Livros, Capítulos e Páginas serão sobrescritas por estas permissões.', 'role_asset_admins' => 'Os administradores recebem automaticamente acesso a todo o conteúdo, mas estas opções podem mostrar ou ocultar as opções da Interface de Usuário.', 'role_asset_image_view_note' => 'Isto está relacionado com a visibilidade do gerenciador de imagens. O acesso real dos arquivos de imagem enviados dependerá da opção de armazenamento de imagens do sistema.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Todos', 'role_own' => 'Próprio', 'role_controlled_by_asset' => 'Controlado pelo ativo para o qual eles são enviados', diff --git a/lang/pt_BR/notifications.php b/lang/pt_BR/notifications.php index 6397b20e5fe..8c98467c81d 100644 --- a/lang/pt_BR/notifications.php +++ b/lang/pt_BR/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Página atualizada: :pageName', 'updated_page_intro' => 'Uma página foi atualizada em :appName:', 'updated_page_debounce' => 'Para prevenir notificações em massa, por enquanto notificações não serão enviadas para você para próximas edições nessa página pelo mesmo editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Nome da Página:', 'detail_page_path' => 'Caminho da Página:', diff --git a/lang/pt_BR/preferences.php b/lang/pt_BR/preferences.php index e0b79ff0644..d2b7fc540a0 100644 --- a/lang/pt_BR/preferences.php +++ b/lang/pt_BR/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Controle as notificações por e-mail que você recebe quando uma determinada atividade é executada no sistema.', 'notifications_opt_own_page_changes' => 'Notificar quando houver alterações em páginas que eu possuo', 'notifications_opt_own_page_comments' => 'Notificar comentários nas páginas que eu possuo', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notificar ao responder aos meus comentários', 'notifications_save' => 'Salvar Preferências', 'notifications_update_success' => 'Preferências de notificação foram atualizadas!', diff --git a/lang/pt_BR/settings.php b/lang/pt_BR/settings.php index b940e57d4c0..53947a36da6 100644 --- a/lang/pt_BR/settings.php +++ b/lang/pt_BR/settings.php @@ -16,7 +16,7 @@ 'app_customization' => 'Customização', 'app_features_security' => 'Recursos & Segurança', 'app_name' => 'Nome da Aplicação', - 'app_name_desc' => 'Esse nome será mostrado no cabeçalho e em e-mails.', + 'app_name_desc' => 'Esse nome será mostrado no cabeçalho e nos e-mails.', 'app_name_header' => 'Mostrar o nome no cabeçalho', 'app_public_access' => 'Acesso Público', 'app_public_access_desc' => 'Habilitar esta opção irá permitir que visitantes, que não estão logados, acessem o conteúdo em sua instância do BookStack.', @@ -29,14 +29,14 @@ 'app_default_editor' => 'Editor de Página Padrão', 'app_default_editor_desc' => 'Selecione qual editor será usado por padrão ao editar novas páginas. Isso pode ser substituído em um nível de página onde é permitido.', 'app_custom_html' => 'Conteúdo customizado para HTML', - 'app_custom_html_desc' => 'Quaisquer conteúdos aqui adicionados serão inseridos no final da seção de cada página. Essa é uma maneira útil de sobrescrever estilos e adicionar códigos de análise de site.', - 'app_custom_html_disabled_notice' => 'O conteúdo customizado do HTML está desabilitado nesta página de configurações, para garantir que quaisquer alterações danosas possam ser revertidas.', + 'app_custom_html_desc' => 'Qualquer conteúdo adicionado aqui será inserido ao final do HTML de todas as páginas. Isso é útil para sobrescrever estilos e adicionar códigos de análise e estatística do site.', + 'app_custom_html_disabled_notice' => 'O conteúdo customizado do HTML está desabilitado nesta página de configurações para garantir que quaisquer alterações danosas possam ser revertidas.', 'app_logo' => 'Logo da Aplicação', 'app_logo_desc' => 'Isto é usado na barra de cabeçalho do aplicativo, entre outras áreas. Esta imagem deve ter 86px de altura. Imagens grandes serão reduzidas.', 'app_icon' => 'Ícone do Aplicativo', 'app_icon_desc' => 'Este ícone é usado para guias e ícones de atalhos do navegador. Deve ser uma imagem PNG quadrada de 256px.', 'app_homepage' => 'Página Inicial', - 'app_homepage_desc' => 'Selecione uma opção para ser exibida como página inicial em vez da padrão. Permissões de página serão ignoradas para as páginas selecionadas.', + 'app_homepage_desc' => 'Selecione uma opção para ser exibida como página inicial no lugar da página padrão. Permissões de página serão ignoradas para as páginas selecionadas.', 'app_homepage_select' => 'Selecione uma página', 'app_footer_links' => 'Links do Rodapé', 'app_footer_links_desc' => 'Adicionar links para mostrar dentro do rodapé do site. Estes serão exibidos na parte inferior da maioria das páginas, incluindo aqueles que não necessitam de login. Você pode usar uma etiqueta de "trans::" para usar traduções definidas pelo sistema. Por exemplo: Usando "trans::common.privacy_policy" fornecerá o texto traduzido "Política de Privacidade" e "trans::common.terms_of_service" fornecerá o texto traduzido "Termos de Serviço".', @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Nenhuma restrição definida', // Sorting Settings - 'sorting' => 'Ordenação', - 'sorting_book_default' => 'Ordenação padrão de livros', + 'sorting' => 'Listas e classificações', + 'sorting_book_default' => 'Regra padrão de classificação de livros', 'sorting_book_default_desc' => 'Selecione a regra de ordenação padrão a ser aplicada a novos livros. Isso não afetará os livros existentes e pode ser substituído para cada livro individualmente.', 'sorting_rules' => 'Regras de ordenação', 'sorting_rules_desc' => 'Estas são operações de ordenação pré-definidas que podem ser aplicadas a conteúdos no sistema.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Data de Atualização', 'sort_rule_op_chapters_first' => 'Capítulos Primeiro', 'sort_rule_op_chapters_last' => 'Capítulos por Último', + 'sorting_page_limits' => 'Limites de exibição por página', + 'sorting_page_limits_desc' => 'Defina quantos itens serão exibidos por página em diferentes listas do sistema. Normalmente, um número menor proporciona melhor desempenho, enquanto um número maior evita a necessidade de clicar em várias páginas. É recomendado o uso de um múltiplo par de 3 (18, 24, 30, etc.).', // Maintenance settings 'maint' => 'Manutenção', @@ -195,11 +197,13 @@ 'role_import_content' => 'Importar conteúdo', 'role_editor_change' => 'Alterar página de edição', 'role_notifications' => 'Receber e gerenciar notificações', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Permissões de Ativos', 'roles_system_warning' => 'Esteja ciente de que o acesso a qualquer uma das três permissões acima pode permitir que um usuário altere seus próprios privilégios ou privilégios de outros usuários no sistema. Apenas atribua perfis com essas permissões para usuários confiáveis.', 'role_asset_desc' => 'Essas permissões controlam o acesso padrão para os ativos dentro do sistema. Permissões em Livros, Capítulos e Páginas serão sobrescritas por essas permissões.', 'role_asset_admins' => 'Administradores recebem automaticamente acesso a todo o conteúdo, mas essas opções podem mostrar ou ocultar as opções da Interface de Usuário.', 'role_asset_image_view_note' => 'Isso está relacionado à visibilidade no gerenciador de imagens. O acesso real dos arquivos de imagem carregados dependerá da opção de armazenamento de imagem do sistema.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Todos', 'role_own' => 'Próprio', 'role_controlled_by_asset' => 'Controlado pelos ativos nos quais o upload foi realizado', diff --git a/lang/ro/notifications.php b/lang/ro/notifications.php index 676eeb814e8..da7f590fd71 100644 --- a/lang/ro/notifications.php +++ b/lang/ro/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Pagina actualizată: :pageName', 'updated_page_intro' => 'O nouă pagină a fost creată în :appName:', 'updated_page_debounce' => 'Pentru a preveni notificări în masă, pentru un timp nu veți primi notificări suplimentare la această pagină de către același editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Nume pagină:', 'detail_page_path' => 'Page Path:', diff --git a/lang/ro/preferences.php b/lang/ro/preferences.php index f7529305c82..93db2f66a8e 100644 --- a/lang/ro/preferences.php +++ b/lang/ro/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Controlați notificările prin e-mail pe care le primiți atunci când o anumită activitate este efectuată în sistem.', 'notifications_opt_own_page_changes' => 'Notifică la comentarii pe paginile pe care le dețin', 'notifications_opt_own_page_comments' => 'Notifică la comentarii pe paginile pe care le dețin', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notifică la răspunsurile la comentariile mele', 'notifications_save' => 'Salvează Preferințe', 'notifications_update_success' => 'Preferințele de notificare au fost actualizate!', diff --git a/lang/ro/settings.php b/lang/ro/settings.php index a28dca22c57..02052ef3cd6 100644 --- a/lang/ro/settings.php +++ b/lang/ro/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Nicio restricție setată', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Mentenanţă', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Schimbă editorul de pagină', 'role_notifications' => 'Primire și gestionare notificări', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Permisiuni active', 'roles_system_warning' => 'Fi conștient de faptul că accesul la oricare dintre cele trei permisiuni de mai sus poate permite unui utilizator să își modifice propriile privilegii sau privilegiile altor persoane din sistem. Atribuie doar roluri cu aceste permisiuni utilizatorilor de încredere.', 'role_asset_desc' => 'Aceste permisiuni controlează accesul implicit la activele din sistem. Permisiunile pe Cărți, Capitole și Pagini vor suprascrie aceste permisiuni.', 'role_asset_admins' => 'Administratorilor li se acordă automat acces la tot conținutul, dar aceste opțiuni pot afișa sau ascunde opțiunile UI.', 'role_asset_image_view_note' => 'Acest lucru se referă la vizibilitatea în managerul de imagini. Accesul efectiv al fișierelor de imagine încărcate va depinde de opțiunea de stocare a imaginilor din sistem.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Tot', 'role_own' => 'Propriu', 'role_controlled_by_asset' => 'Controlat de activele pe care sunt încărcate', diff --git a/lang/ru/notifications.php b/lang/ru/notifications.php index c5e98da80ef..289de42b6c0 100644 --- a/lang/ru/notifications.php +++ b/lang/ru/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Обновлена страница: :pageName', 'updated_page_intro' => 'Страница была обновлена в :appName:', 'updated_page_debounce' => 'Чтобы предотвратить массовые уведомления, в течение некоторого времени вы не будете получать уведомления о дальнейших правках этой страницы этим же редактором.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Имя страницы:', 'detail_page_path' => 'Путь страницы:', diff --git a/lang/ru/preferences.php b/lang/ru/preferences.php index 27217815d13..b61b252c8dc 100644 --- a/lang/ru/preferences.php +++ b/lang/ru/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Управляйте полученными по электронной почте уведомлениями при выполнении определенных действий в системе.', 'notifications_opt_own_page_changes' => 'Уведомлять об изменениях в собственных страницах', 'notifications_opt_own_page_comments' => 'Уведомлять о комментариях на собственных страницах', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Уведомлять об ответах на мои комментарии', 'notifications_save' => 'Сохранить настройки', 'notifications_update_success' => 'Настройки уведомлений были обновлены!', diff --git a/lang/ru/settings.php b/lang/ru/settings.php index 69c13e46dd3..47839d52006 100644 --- a/lang/ru/settings.php +++ b/lang/ru/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Без ограничений', // Sorting Settings - 'sorting' => 'Сортировка', - 'sorting_book_default' => 'Сортировка книг по умолчанию', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Выберите правило сортировки по умолчанию для новых книг. Это не повлияет на существующие книги, и может быть изменено для каждой книги отдельно.', 'sorting_rules' => 'Правила сортировки', 'sorting_rules_desc' => 'Выберите правило сортировки по умолчанию для новых книг. Это не повлияет на существующие книги и может быть изменено для каждой книги отдельно.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Главы в конце', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Обслуживание', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Изменение редактора страниц', 'role_notifications' => 'Получение и управление уведомлениями', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Права доступа к материалам', 'roles_system_warning' => 'Имейте в виду, что доступ к любому из указанных выше трех разрешений может позволить пользователю изменить свои собственные привилегии или привилегии других пользователей системы. Назначать роли с этими правами можно только доверенным пользователям.', 'role_asset_desc' => 'Эти разрешения контролируют доступ по умолчанию к параметрам внутри системы. Разрешения на книги, главы и страницы перезапишут эти разрешения.', 'role_asset_admins' => 'Администраторы автоматически получают доступ ко всему контенту, но эти опции могут отображать или скрывать параметры пользовательского интерфейса.', 'role_asset_image_view_note' => 'Это относится к видимости в менеджере изображений. Фактический доступ к загруженным файлам изображений будет зависеть от опции хранения системных изображений.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Все', 'role_own' => 'Владелец', 'role_controlled_by_asset' => 'Контролируется активом, в который они загружены', diff --git a/lang/sk/notifications.php b/lang/sk/notifications.php index 1afd23f1dc4..563ac24e84d 100644 --- a/lang/sk/notifications.php +++ b/lang/sk/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Updated page: :pageName', 'updated_page_intro' => 'A page has been updated in :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Page Name:', 'detail_page_path' => 'Page Path:', diff --git a/lang/sk/preferences.php b/lang/sk/preferences.php index 85da4f331f7..54fbdb677a6 100644 --- a/lang/sk/preferences.php +++ b/lang/sk/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/sk/settings.php b/lang/sk/settings.php index 2731fd01a2b..04855a7f96e 100644 --- a/lang/sk/settings.php +++ b/lang/sk/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Nie sú nastavené žiadne obmedzenia', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Údržba', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Zmeniť editor stránky', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Oprávnenia majetku', 'roles_system_warning' => 'Uvedomte si, že prístup ku ktorémukoľvek z vyššie uvedených troch povolení môže používateľovi umožniť zmeniť svoje vlastné privilégiá alebo privilégiá ostatných v systéme. Roly s týmito povoleniami priraďujte iba dôveryhodným používateľom.', 'role_asset_desc' => 'Tieto oprávnenia regulujú prednastavený prístup k zdroju v systéme. Oprávnenia pre knihy, kapitoly a stránky majú vyššiu prioritu.', 'role_asset_admins' => 'Správcovia majú automaticky prístup ku všetkému obsahu, ale tieto možnosti môžu zobraziť alebo skryť možnosti používateľského rozhrania.', 'role_asset_image_view_note' => 'Toto sa týka viditeľnosti v rámci správcu obrázkov. Skutočný prístup k nahratým súborom obrázkov bude závisieť od možnosti ukladania obrázkov systému.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Všetko', 'role_own' => 'Vlastné', 'role_controlled_by_asset' => 'Regulované zdrojom, do ktorého sú nahrané', diff --git a/lang/sl/notifications.php b/lang/sl/notifications.php index 1afd23f1dc4..563ac24e84d 100644 --- a/lang/sl/notifications.php +++ b/lang/sl/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Updated page: :pageName', 'updated_page_intro' => 'A page has been updated in :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Page Name:', 'detail_page_path' => 'Page Path:', diff --git a/lang/sl/preferences.php b/lang/sl/preferences.php index 2872f5f3c65..f4459d738e4 100644 --- a/lang/sl/preferences.php +++ b/lang/sl/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/sl/settings.php b/lang/sl/settings.php index 904978f4705..6eaed0a1702 100644 --- a/lang/sl/settings.php +++ b/lang/sl/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Brez omejitev', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Vzdrževanje', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Change page editor', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Sistemska dovoljenja', 'roles_system_warning' => 'Zavedajte se, da lahko dostop do kateregakoli od zgornjih treh dovoljenj uporabniku omogoči, da spremeni lastne privilegije ali privilegije drugih v sistemu. Vloge s temi dovoljenji dodelite samo zaupanja vrednim uporabnikom.', 'role_asset_desc' => 'Ta dovoljenja nadzorujejo privzeti dostop do sredstev v sistemu. Dovoljenja za knjige, poglavja in strani bodo razveljavila ta dovoljenja.', 'role_asset_admins' => 'Skrbniki samodejno pridobijo dostop do vseh vsebin, vendar lahko te možnosti prikažejo ali pa skrijejo možnosti uporabniškega vmesnika.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Vse', 'role_own' => 'Lasten', 'role_controlled_by_asset' => 'Nadzira ga sredstvo, v katerega so naloženi', diff --git a/lang/sq/notifications.php b/lang/sq/notifications.php index 1afd23f1dc4..563ac24e84d 100644 --- a/lang/sq/notifications.php +++ b/lang/sq/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Updated page: :pageName', 'updated_page_intro' => 'A page has been updated in :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Page Name:', 'detail_page_path' => 'Page Path:', diff --git a/lang/sq/preferences.php b/lang/sq/preferences.php index 2872f5f3c65..f4459d738e4 100644 --- a/lang/sq/preferences.php +++ b/lang/sq/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/sq/settings.php b/lang/sq/settings.php index 81c2c0a93c3..c68605fe1f8 100644 --- a/lang/sq/settings.php +++ b/lang/sq/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'No restriction set', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Maintenance', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Change page editor', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Asset Permissions', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', 'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'All', 'role_own' => 'Own', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', diff --git a/lang/sr/activities.php b/lang/sr/activities.php index ad594a2204e..b4aa5e2a7f4 100644 --- a/lang/sr/activities.php +++ b/lang/sr/activities.php @@ -85,9 +85,9 @@ 'webhook_delete_notification' => 'Вебхоок је успешно обрисан', // Imports - 'import_create' => 'created import', + 'import_create' => 'креиран увоз', 'import_create_notification' => 'Import successfully uploaded', - 'import_run' => 'updated import', + 'import_run' => 'ажуриран увоз', 'import_run_notification' => 'Content successfully imported', 'import_delete' => 'deleted import', 'import_delete_notification' => 'Import successfully deleted', diff --git a/lang/sr/common.php b/lang/sr/common.php index e185c99f5bf..c5c62db6588 100644 --- a/lang/sr/common.php +++ b/lang/sr/common.php @@ -30,7 +30,7 @@ 'create' => 'Креирај', 'update' => 'Ажурирање', 'edit' => 'Уреди', - 'archive' => 'Archive', + 'archive' => 'Архивирај', 'unarchive' => 'Un-Archive', 'sort' => 'Разврстај', 'move' => 'Премести', diff --git a/lang/sr/editor.php b/lang/sr/editor.php index e5052595ceb..2756775a9f2 100644 --- a/lang/sr/editor.php +++ b/lang/sr/editor.php @@ -13,7 +13,7 @@ 'cancel' => 'Поништи', 'save' => 'Сачувај', 'close' => 'Затвори', - 'apply' => 'Apply', + 'apply' => 'Примени', 'undo' => 'Опозови', 'redo' => 'Понови', 'left' => 'Лево', diff --git a/lang/sr/entities.php b/lang/sr/entities.php index 456a76ceb17..151edee40fa 100644 --- a/lang/sr/entities.php +++ b/lang/sr/entities.php @@ -50,7 +50,7 @@ 'import_zip_validation_errors' => 'Errors were detected while validating the provided ZIP file:', 'import_pending' => 'Pending Imports', 'import_pending_none' => 'No imports have been started.', - 'import_continue' => 'Continue Import', + 'import_continue' => 'Настави увоз', 'import_continue_desc' => 'Review the content due to be imported from the uploaded ZIP file. When ready, run the import to add its contents to this system. The uploaded ZIP import file will be automatically removed on successful import.', 'import_details' => 'Import Details', 'import_run' => 'Run Import', @@ -109,7 +109,7 @@ // Shelves 'shelf' => 'Shelf', - 'shelves' => 'Shelves', + 'shelves' => 'Полице', 'x_shelves' => ':count Shelf|:count Shelves', 'shelves_empty' => 'No shelves have been created', 'shelves_create' => 'Create New Shelf', diff --git a/lang/sr/notifications.php b/lang/sr/notifications.php index 6aa3f2abbaa..4cc499fdd40 100644 --- a/lang/sr/notifications.php +++ b/lang/sr/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Ажурирана страница: :pageName', 'updated_page_intro' => 'Страница је ажурирана у :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Назив странице:', 'detail_page_path' => 'Путања странице:', diff --git a/lang/sr/preferences.php b/lang/sr/preferences.php index 2872f5f3c65..f4459d738e4 100644 --- a/lang/sr/preferences.php +++ b/lang/sr/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/sr/settings.php b/lang/sr/settings.php index b7213e905b0..d34ff3f3b7d 100644 --- a/lang/sr/settings.php +++ b/lang/sr/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Нема постављених ограничења', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Одржавање', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Change page editor', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Asset Permissions', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', 'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'All', 'role_own' => 'Own', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', diff --git a/lang/sr/validation.php b/lang/sr/validation.php index d9b982d1e23..a2a617633ce 100644 --- a/lang/sr/validation.php +++ b/lang/sr/validation.php @@ -14,7 +14,7 @@ 'alpha' => 'The :attribute may only contain letters.', 'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.', 'alpha_num' => 'The :attribute may only contain letters and numbers.', - 'array' => 'The :attribute must be an array.', + 'array' => ':attribute мора бити низ.', 'backup_codes' => 'The provided code is not valid or has already been used.', 'before' => 'The :attribute must be a date before :date.', 'between' => [ diff --git a/lang/sv/notifications.php b/lang/sv/notifications.php index 58418ddb01e..19933c049cb 100644 --- a/lang/sv/notifications.php +++ b/lang/sv/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Uppdaterad sida: :pageName', 'updated_page_intro' => 'En sida har blivit uppdaterad i :appName:', 'updated_page_debounce' => 'För att förhindra en massa notiser, så kommer det inte skickas nya notiser på ett tag för ytterligare ändringar till denna sida av samma skribent.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Sidonamn:', 'detail_page_path' => 'Sidosökväg:', diff --git a/lang/sv/preferences.php b/lang/sv/preferences.php index 945099151f2..492081e59e3 100644 --- a/lang/sv/preferences.php +++ b/lang/sv/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/sv/settings.php b/lang/sv/settings.php index 8f391d3d713..2e86241dae4 100644 --- a/lang/sv/settings.php +++ b/lang/sv/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Ingen begränsning inställd', // Sorting Settings - 'sorting' => 'Sorterar', - 'sorting_book_default' => 'Standard boksortering', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Välj standard sorteringsregel som skall tillämpas på nya böcker. Detta påverkar inte befintliga böcker och kan åsidosättas per bok.', 'sorting_rules' => 'Sorteringsregler', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Datum uppdaterat', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Underhåll', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Ändra sidredigerare', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Tillgång till innehåll', 'roles_system_warning' => 'Var medveten om att åtkomst till någon av ovanstående tre behörigheter kan tillåta en användare att ändra sina egna rättigheter eller andras rättigheter i systemet. Tilldela endast roller med dessa behörigheter till betrodda användare.', 'role_asset_desc' => 'Det här är standardinställningarna för allt innehåll i systemet. Eventuella anpassade rättigheter på böcker, kapitel och sidor skriver över dessa inställningar.', 'role_asset_admins' => 'Administratörer har automatisk tillgång till allt innehåll men dessa alternativ kan visa och dölja vissa gränssnittselement', 'role_asset_image_view_note' => 'Detta avser synlighet inom bildhanteraren. Faktisk åtkomst för uppladdade bildfiler kommer att bero på alternativ för bildlagring.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Alla', 'role_own' => 'Egna', 'role_controlled_by_asset' => 'Kontrolleras av den sida de laddas upp till', diff --git a/lang/tk/notifications.php b/lang/tk/notifications.php index 1afd23f1dc4..563ac24e84d 100644 --- a/lang/tk/notifications.php +++ b/lang/tk/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Updated page: :pageName', 'updated_page_intro' => 'A page has been updated in :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Page Name:', 'detail_page_path' => 'Page Path:', diff --git a/lang/tk/preferences.php b/lang/tk/preferences.php index 2872f5f3c65..f4459d738e4 100644 --- a/lang/tk/preferences.php +++ b/lang/tk/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/tk/settings.php b/lang/tk/settings.php index 81c2c0a93c3..c68605fe1f8 100644 --- a/lang/tk/settings.php +++ b/lang/tk/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'No restriction set', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Maintenance', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Change page editor', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Asset Permissions', 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', 'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'All', 'role_own' => 'Own', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', diff --git a/lang/tr/notifications.php b/lang/tr/notifications.php index c26a90b32cb..5dfb719783e 100644 --- a/lang/tr/notifications.php +++ b/lang/tr/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Updated page: :pageName', 'updated_page_intro' => 'A page has been updated in :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Page Name:', 'detail_page_path' => 'Page Path:', diff --git a/lang/tr/preferences.php b/lang/tr/preferences.php index 1ce7b01fda4..f4ec771b2d7 100644 --- a/lang/tr/preferences.php +++ b/lang/tr/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/tr/settings.php b/lang/tr/settings.php index 1eb15d9d4c0..71d56000fc1 100644 --- a/lang/tr/settings.php +++ b/lang/tr/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Hiçbir kısıtlama tanımlanmamış', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Bakım', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Yazı editörünü değiştir', 'role_notifications' => 'Receive & manage notifications', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Varlık Yetkileri', 'roles_system_warning' => 'Yukarıdaki üç izinden herhangi birine erişimin, kullanıcının kendi ayrıcalıklarını veya sistemdeki diğerlerinin ayrıcalıklarını değiştirmesine izin verebileceğini unutmayın. Yalnızca bu izinlere sahip rolleri güvenilir kullanıcılara atayın.', 'role_asset_desc' => 'Bu izinler, sistem içindeki varlıklara varsayılan erişim izinlerini ayarlar. Kitaplar, bölümler ve sayfalar üzerindeki izinler, buradaki izinleri geçersiz kılar.', 'role_asset_admins' => 'Yöneticilere otomatik olarak bütün içeriğe erişim yetkisi verilir ancak bu seçenekler, kullanıcı arayüzündeki bazı seçeneklerin gösterilmesine veya gizlenmesine neden olabilir.', 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Hepsi', 'role_own' => 'Kendine Ait', 'role_controlled_by_asset' => 'Yüklendikleri varlık tarafından kontrol ediliyor', diff --git a/lang/uk/notifications.php b/lang/uk/notifications.php index a08b9a100be..d40457f98fc 100644 --- a/lang/uk/notifications.php +++ b/lang/uk/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Оновлено сторінку: :pageName', 'updated_page_intro' => 'Оновлено сторінку у :appName:', 'updated_page_debounce' => 'Для запобігання кількості сповіщень, деякий час ви не будете відправлені повідомлення для подальших змін на цій сторінці тим самим редактором.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Назва сторінки:', 'detail_page_path' => 'Шлях до сторінки:', diff --git a/lang/uk/preferences.php b/lang/uk/preferences.php index 14989d2a740..8af3a8d9e55 100644 --- a/lang/uk/preferences.php +++ b/lang/uk/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Контролюйте сповіщення по електронній пошті, які ви отримуєте, коли виконується певна активність у системі.', 'notifications_opt_own_page_changes' => 'Повідомляти при змінах сторінок якими я володію', 'notifications_opt_own_page_comments' => 'Повідомляти при коментарях на моїх сторінках', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Повідомляти про відповіді на мої коментарі', 'notifications_save' => 'Зберегти налаштування', 'notifications_update_success' => 'Налаштування сповіщень було оновлено!', diff --git a/lang/uk/settings.php b/lang/uk/settings.php index 3798f1b6078..633582ca834 100644 --- a/lang/uk/settings.php +++ b/lang/uk/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Не встановлено обмежень', // Sorting Settings - 'sorting' => 'Сортування', - 'sorting_book_default' => 'Типовий порядок сортування', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Виберіть правило сортування за замовчуванням для застосування нових книг. Це не вплине на існуючі книги, і може бути перевизначено для кожної книги.', 'sorting_rules' => 'Сортувати правила', 'sorting_rules_desc' => 'Це попередньо визначені операції сортування, які можуть бути застосовані до вмісту в системі.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Дата оновлення', 'sort_rule_op_chapters_first' => 'Спочатку розділи', 'sort_rule_op_chapters_last' => 'Розділи останні', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Обслуговування', @@ -195,11 +197,13 @@ 'role_import_content' => 'Імпортувати вміст', 'role_editor_change' => 'Змінити редактор сторінок', 'role_notifications' => 'Отримувати та керувати повідомленнями', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Дозволи', 'roles_system_warning' => 'Майте на увазі, що доступ до будь-якого з вищезазначених трьох дозволів може дозволити користувачеві змінювати власні привілеї або привілеї інших в системі. Ролі з цими дозволами призначайте лише довіреним користувачам.', 'role_asset_desc' => 'Ці дозволи контролюють стандартні доступи всередині системи. Права на книги, розділи та сторінки перевизначать ці дозволи.', 'role_asset_admins' => 'Адміністратори автоматично отримують доступ до всього вмісту, але ці параметри можуть відображати або приховувати параметри інтерфейсу користувача.', 'role_asset_image_view_note' => 'Це стосується видимості в менеджері зображень. Фактичний доступ завантажуваних зображень буде залежний від опції зберігання системних зображень.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Все', 'role_own' => 'Власне', 'role_controlled_by_asset' => 'Контролюється за об\'єктом, до якого вони завантажуються', diff --git a/lang/uz/notifications.php b/lang/uz/notifications.php index bec9b3925e2..ece09441e9c 100644 --- a/lang/uz/notifications.php +++ b/lang/uz/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => ':pageName sahifasi yangilandi', 'updated_page_intro' => ':appName ichida sahifa yangilandi:', 'updated_page_debounce' => 'Xabarnomalar koʻp boʻlishining oldini olish uchun bir muncha vaqt oʻsha muharrir tomonidan ushbu sahifaga keyingi tahrirlar haqida bildirishnomalar yuborilmaydi.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Sahifa nomi:', 'detail_page_path' => 'Page Path:', diff --git a/lang/uz/preferences.php b/lang/uz/preferences.php index de36b953e85..996f9d3c4bb 100644 --- a/lang/uz/preferences.php +++ b/lang/uz/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Tizimda muayyan harakatlar amalga oshirilganda qabul qilinadigan elektron pochta xabarnomalarini boshqaring.', 'notifications_opt_own_page_changes' => 'Menga tegishli boʻlgan sahifalarimdagi oʻzgarishlar haqida xabar bering', 'notifications_opt_own_page_comments' => 'Menga tegishli sahifalardagi sharhlar haqida xabar bering', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Fikrlarimga javoblarim haqida xabar bering', 'notifications_save' => 'Afzalliklarni saqlash', 'notifications_update_success' => 'Bildirishnoma sozlamalari yangilandi!', diff --git a/lang/uz/settings.php b/lang/uz/settings.php index 83fedc569ef..ad191143f40 100644 --- a/lang/uz/settings.php +++ b/lang/uz/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Cheklov oʻrnatilmagan', // Sorting Settings - 'sorting' => 'Sorting', - 'sorting_book_default' => 'Default Book Sort', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', 'sorting_rules' => 'Sort Rules', 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Updated Date', 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Xizmat', @@ -195,11 +197,13 @@ 'role_import_content' => 'Import content', 'role_editor_change' => 'Sahifa muharririni o\'zgartirish', 'role_notifications' => 'Bildirishnomalarni qabul qilish va boshqarish', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Obyektga ruxsatlar', 'roles_system_warning' => 'Shuni yodda tutingki, yuqoridagi uchta ruxsatdan birortasiga kirish foydalanuvchiga o\'z imtiyozlarini yoki tizimdagi boshqalarning imtiyozlarini o\'zgartirishi mumkin. Ishonchli foydalanuvchilarga faqat ushbu ruxsatlarga ega rollarni tayinlang.', 'role_asset_desc' => 'Bu ruxsatlar tizim ichidagi aktivlarga standart kirishni nazorat qiladi. Kitoblar, boblar va sahifalardagi ruxsatlar bu ruxsatlarni bekor qiladi.', 'role_asset_admins' => 'Administratorlarga avtomatik ravishda barcha kontentga kirish huquqi beriladi, lekin bu parametrlar UI parametrlarini koʻrsatishi yoki yashirishi mumkin.', 'role_asset_image_view_note' => 'Bu tasvir menejeridagi ko\'rinishga tegishli. Yuklangan rasm fayllariga haqiqiy kirish tizim tasvirini saqlash opsiyasiga bog\'liq bo\'ladi.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Hammasi', 'role_own' => 'Shaxsiy', 'role_controlled_by_asset' => 'Ular yuklangan obyekt tomonidan nazorat qilinadi', diff --git a/lang/vi/notifications.php b/lang/vi/notifications.php index a18695b2347..45fb4434ffa 100644 --- a/lang/vi/notifications.php +++ b/lang/vi/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => 'Trang đã cập nhật: :pageName', 'updated_page_intro' => 'Một trang mới đã được cập nhật trong :appName:', 'updated_page_debounce' => 'Để tránh việc nhận quá nhiều thông báo, trong một thời gian, bạn sẽ không nhận được thông báo về những chỉnh sửa tiếp theo cho trang này từ cùng một biên tập viên.', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Tên Trang:', 'detail_page_path' => 'Đường dẫn trang:', diff --git a/lang/vi/preferences.php b/lang/vi/preferences.php index 76bc0be16a2..3e06ccd3026 100644 --- a/lang/vi/preferences.php +++ b/lang/vi/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', 'notifications_save' => 'Save Preferences', 'notifications_update_success' => 'Notification preferences have been updated!', diff --git a/lang/vi/settings.php b/lang/vi/settings.php index 1ff252702c0..69eadddd36f 100644 --- a/lang/vi/settings.php +++ b/lang/vi/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Không có giới hạn nào được thiết lập', // Sorting Settings - 'sorting' => 'Sắp xếp', - 'sorting_book_default' => 'Sắp xếp sách mặc định', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Chọn quy tắc sắp xếp mặc định để áp dụng cho sách mới. Điều này sẽ không ảnh hưởng đến các sách hiện có và có thể được ghi đè cho từng sách.', 'sorting_rules' => 'Quy tắc sắp xếp', 'sorting_rules_desc' => 'Đây là các thao tác sắp xếp được xác định trước có thể được áp dụng cho nội dung trong hệ thống.', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => 'Ngày cập nhật', 'sort_rule_op_chapters_first' => 'Chương trước', 'sort_rule_op_chapters_last' => 'Chương sau', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => 'Bảo trì', @@ -195,11 +197,13 @@ 'role_import_content' => 'Nhập nội dung', 'role_editor_change' => 'Thay đổi trình soạn thảo trang', 'role_notifications' => 'Nhận & quản lý thông báo', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => 'Quyền tài sản (asset)', 'roles_system_warning' => 'Cần lưu ý rằng việc truy cập vào bất kỳ ba quyền trên có thể cho phép người dùng thay đổi đặc quyền của chính họ hoặc đặc quyền của những người khác trong hệ thống. Chỉ gán các vai trò có các quyền này cho những người dùng đáng tin cậy.', 'role_asset_desc' => 'Các quyền này điều khiển truy cập mặc định tới tài sản (asset) nằm trong hệ thống. Quyền tại Sách, Chương và Trang sẽ ghi đè các quyền này.', 'role_asset_admins' => 'Quản trị viên được tự động cấp quyền truy cập đến toàn bộ nội dung, tuy nhiên các tùy chọn đó có thể hiện hoặc ẩn tùy chọn giao diện.', 'role_asset_image_view_note' => 'Điều này liên quan đến khả năng hiển thị trong trình quản lý hình ảnh. Quyền truy cập thực tế vào các tệp hình ảnh đã tải lên sẽ phụ thuộc vào tùy chọn lưu trữ hình ảnh của hệ thống.', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => 'Tất cả', 'role_own' => 'Sở hữu', 'role_controlled_by_asset' => 'Kiểm soát các tài sản (asset) người dùng tải lên', diff --git a/lang/zh_CN/notifications.php b/lang/zh_CN/notifications.php index 52c9822bc2a..e4eebf5cc56 100644 --- a/lang/zh_CN/notifications.php +++ b/lang/zh_CN/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => '页面更新::pageName', 'updated_page_intro' => ':appName: 中的一个页面已被更新', 'updated_page_debounce' => '为了防止出现大量通知,一段时间内您不会收到同一编辑者再次编辑本页面的通知。', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => '页面名称:', 'detail_page_path' => '页面路径:', diff --git a/lang/zh_CN/preferences.php b/lang/zh_CN/preferences.php index f1ef3957d1e..f89448dd366 100644 --- a/lang/zh_CN/preferences.php +++ b/lang/zh_CN/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => '控制在系统内发生某些活动时您会收到的电子邮件通知。', 'notifications_opt_own_page_changes' => '在我拥有的页面被修改时通知我', 'notifications_opt_own_page_comments' => '在我拥有的页面上有新评论时通知我', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => '在有人回复我的频率时通知我', 'notifications_save' => '保存偏好设置', 'notifications_update_success' => '通知偏好设置已更新!', diff --git a/lang/zh_CN/settings.php b/lang/zh_CN/settings.php index 2114c36480c..93f3076c595 100644 --- a/lang/zh_CN/settings.php +++ b/lang/zh_CN/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => '尚未设置限制', // Sorting Settings - 'sorting' => '排序', - 'sorting_book_default' => '默认书卷排序', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => '选择要应用于新书的默认排序规则。这不会影响现有书,并且可以每本书覆盖。', 'sorting_rules' => '排序规则', 'sorting_rules_desc' => '这些是预定义的排序操作,可应用于系统中的内容。', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => '更新时间', 'sort_rule_op_chapters_first' => '章节正序', 'sort_rule_op_chapters_last' => '章节倒序', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => '维护', @@ -195,11 +197,13 @@ 'role_import_content' => '导入内容', 'role_editor_change' => '更改页面编辑器', 'role_notifications' => '管理和接收通知', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => '资源许可', 'roles_system_warning' => '请注意,拥有以上三个权限中的任何一个都会允许用户更改自己的权限或系统中其他人的权限。 请只将拥有这些权限的角色分配给你信任的用户。', 'role_asset_desc' => '对系统内资源的默认访问许可将由这些权限控制。单独设置在书籍、章节和页面上的权限将覆盖这里的权限设定。', 'role_asset_admins' => '管理员可自动获得对所有内容的访问权限,但这些选项可能会显示或隐藏UI选项。', 'role_asset_image_view_note' => '这与图像管理器中的可见性有关。已经上传的图片的实际访问取决于系统图像存储选项。', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => '全部的', 'role_own' => '拥有的', 'role_controlled_by_asset' => '由其所在的资源来控制', diff --git a/lang/zh_TW/notifications.php b/lang/zh_TW/notifications.php index 7fcfa640090..f5deae3284a 100644 --- a/lang/zh_TW/notifications.php +++ b/lang/zh_TW/notifications.php @@ -11,6 +11,8 @@ 'updated_page_subject' => '頁面更新::pageName', 'updated_page_intro' => ':appName: 中的一個頁面已被更新', 'updated_page_debounce' => '為了防止出現大量通知,一段時間內您不會收到同一編輯者再次編輯本頁面的通知。', + 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => '頁面名稱:', 'detail_page_path' => '頁面路徑:', diff --git a/lang/zh_TW/preferences.php b/lang/zh_TW/preferences.php index 4900ca15264..0bfce9d832d 100644 --- a/lang/zh_TW/preferences.php +++ b/lang/zh_TW/preferences.php @@ -23,6 +23,7 @@ 'notifications_desc' => '控制在系統有特定活動時,是否要接收電子郵件通知', 'notifications_opt_own_page_changes' => '當我的頁面有異動時發送通知', 'notifications_opt_own_page_comments' => '當我的頁面有評論時發送通知', + 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => '當我的評論有新的回覆時發送通知', 'notifications_save' => '儲存偏好設定', 'notifications_update_success' => '通知設定已更新', diff --git a/lang/zh_TW/settings.php b/lang/zh_TW/settings.php index 752d3f2a74e..9b5efa09e39 100644 --- a/lang/zh_TW/settings.php +++ b/lang/zh_TW/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => '尚未設定限制', // Sorting Settings - 'sorting' => '排序', - 'sorting_book_default' => '預設書籍排序', + 'sorting' => 'Lists & Sorting', + 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => '選取要套用至新書籍的預設排序規則。這不會影響現有書籍,並可按書籍覆寫。', 'sorting_rules' => '排序規則', 'sorting_rules_desc' => '這些是預先定義的排序作業,可套用於系統中的內容。', @@ -103,6 +103,8 @@ 'sort_rule_op_updated_date' => '更新日期', 'sort_rule_op_chapters_first' => '第一章', 'sort_rule_op_chapters_last' => '最後一章', + 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', // Maintenance settings 'maint' => '維護', @@ -196,11 +198,13 @@ 'role_import_content' => '匯入內容', 'role_editor_change' => '重設頁面編輯器', 'role_notifications' => '管理和接收通知', + 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', 'role_asset' => '資源權限', 'roles_system_warning' => '請注意,有上述三項權限中的任一項的使用者都可以更改自己或系統中其他人的權限。有這些權限的角色只應分配給受信任的使用者。', 'role_asset_desc' => '對系統內資源的預設權限將由這裡的權限控制。若有單獨設定在書本、章節和頁面上的權限,將會覆寫這裡的權限設定。', 'role_asset_admins' => '管理員會自動取得對所有內容的存取權,但這些選項可能會顯示或隱藏使用者介面的選項。', 'role_asset_image_view_note' => '這與圖像管理器中的可見性有關。已經上傳的圖片的實際訪問取決於系統圖像存儲選項。', + 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', 'role_all' => '全部', 'role_own' => '擁有', 'role_controlled_by_asset' => '依據隸屬的資源來決定', From 88d86df66f3eca1e0cd67c052aa1a99116d4d557 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 29 Dec 2025 23:08:18 +0000 Subject: [PATCH 012/204] ZIP Exports: Added limit to ZIP file size before extraction Checks files within the ZIP again the app upload file limit before using/streaming/extracting, to help ensure that they do no exceed what might be expected on that instance, and to prevent disk exhaustion via things like super high compression ratio files. Thanks to Jeong Woo Lee (eclipse07077-ljw) for reporting. --- app/Exports/ZipExports/ZipExportReader.php | 21 ++++++++ .../ZipExports/ZipFileReferenceRule.php | 8 ++- app/Exports/ZipExports/ZipImportRunner.php | 6 +++ lang/en/errors.php | 1 + lang/en/validation.php | 1 + tests/Exports/ZipImportRunnerTest.php | 53 +++++++++++++++++++ 6 files changed, 89 insertions(+), 1 deletion(-) diff --git a/app/Exports/ZipExports/ZipExportReader.php b/app/Exports/ZipExports/ZipExportReader.php index c3d5c23cfec..28b830167c8 100644 --- a/app/Exports/ZipExports/ZipExportReader.php +++ b/app/Exports/ZipExports/ZipExportReader.php @@ -58,6 +58,16 @@ public function readData(): array { $this->open(); + $info = $this->zip->statName('data.json'); + if ($info === false) { + throw new ZipExportException(trans('errors.import_zip_cant_decode_data')); + } + + $maxSize = max(intval(config()->get('app.upload_limit')), 1) * 1000000; + if ($info['size'] > $maxSize) { + throw new ZipExportException(trans('errors.import_zip_data_too_large')); + } + // Validate json data exists, including metadata $jsonData = $this->zip->getFromName('data.json') ?: ''; $importData = json_decode($jsonData, true); @@ -73,6 +83,17 @@ public function fileExists(string $fileName): bool return $this->zip->statName("files/{$fileName}") !== false; } + public function fileWithinSizeLimit(string $fileName): bool + { + $fileInfo = $this->zip->statName("files/{$fileName}"); + if ($fileInfo === false) { + return false; + } + + $maxSize = max(intval(config()->get('app.upload_limit')), 1) * 1000000; + return $fileInfo['size'] <= $maxSize; + } + /** * @return false|resource */ diff --git a/app/Exports/ZipExports/ZipFileReferenceRule.php b/app/Exports/ZipExports/ZipFileReferenceRule.php index 90e78c060b0..01d703a1d0a 100644 --- a/app/Exports/ZipExports/ZipFileReferenceRule.php +++ b/app/Exports/ZipExports/ZipFileReferenceRule.php @@ -13,7 +13,6 @@ public function __construct( ) { } - /** * @inheritDoc */ @@ -23,6 +22,13 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $fail('validation.zip_file')->translate(); } + if (!$this->context->zipReader->fileWithinSizeLimit($value)) { + $fail('validation.zip_file_size')->translate([ + 'attribute' => $value, + 'size' => config('app.upload_limit'), + ]); + } + if (!empty($this->acceptedMimes)) { $fileMime = $this->context->zipReader->sniffFileMime($value); if (!in_array($fileMime, $this->acceptedMimes)) { diff --git a/app/Exports/ZipExports/ZipImportRunner.php b/app/Exports/ZipExports/ZipImportRunner.php index 748acf43f74..382e4073eec 100644 --- a/app/Exports/ZipExports/ZipImportRunner.php +++ b/app/Exports/ZipExports/ZipImportRunner.php @@ -265,6 +265,12 @@ protected function exportTagsToInputArray(array $exportTags): array protected function zipFileToUploadedFile(string $fileName, ZipExportReader $reader): UploadedFile { + if (!$reader->fileWithinSizeLimit($fileName)) { + throw new ZipImportException([ + "File $fileName exceeds app upload limit." + ]); + } + $tempPath = tempnam(sys_get_temp_dir(), 'bszipextract'); $fileStream = $reader->streamFile($fileName); $tempStream = fopen($tempPath, 'wb'); diff --git a/lang/en/errors.php b/lang/en/errors.php index 9d738379648..77d7ee69e49 100644 --- a/lang/en/errors.php +++ b/lang/en/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/en/validation.php b/lang/en/validation.php index d9b982d1e23..ff028525df3 100644 --- a/lang/en/validation.php +++ b/lang/en/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/tests/Exports/ZipImportRunnerTest.php b/tests/Exports/ZipImportRunnerTest.php index 2255e16c393..67c1a90e528 100644 --- a/tests/Exports/ZipImportRunnerTest.php +++ b/tests/Exports/ZipImportRunnerTest.php @@ -5,6 +5,7 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; +use BookStack\Exceptions\ZipImportException; use BookStack\Exports\ZipExports\ZipImportRunner; use BookStack\Uploads\Image; use Tests\TestCase; @@ -431,4 +432,56 @@ public function test_drawing_references_are_updated_within_content() ZipTestHelper::deleteZipForImport($import); } + + public function test_error_thrown_if_zip_item_exceeds_app_file_upload_limit() + { + $tempFile = tempnam(sys_get_temp_dir(), 'bs-zip-test'); + file_put_contents($tempFile, str_repeat('a', 2500000)); + $parent = $this->entities->chapter(); + config()->set('app.upload_limit', 1); + + $import = ZipTestHelper::importFromData([], [ + 'page' => [ + 'name' => 'Page A', + 'html' => '

    Hello

    ', + 'attachments' => [ + [ + 'name' => 'Text attachment', + 'file' => 'file_attachment' + ] + ], + ], + ], [ + 'file_attachment' => $tempFile, + ]); + + $this->asAdmin(); + + $this->expectException(ZipImportException::class); + $this->expectExceptionMessage('The file file_attachment must not exceed 1 MB.'); + + $this->runner->run($import, $parent); + ZipTestHelper::deleteZipForImport($import); + } + + public function test_error_thrown_if_zip_data_exceeds_app_file_upload_limit() + { + $parent = $this->entities->chapter(); + config()->set('app.upload_limit', 1); + + $import = ZipTestHelper::importFromData([], [ + 'page' => [ + 'name' => 'Page A', + 'html' => '

    ' . str_repeat('a', 2500000) . '

    ', + ], + ]); + + $this->asAdmin(); + + $this->expectException(ZipImportException::class); + $this->expectExceptionMessage('ZIP data.json content exceeds the configured application maximum upload size.'); + + $this->runner->run($import, $parent); + ZipTestHelper::deleteZipForImport($import); + } } From b08d1b36de36d96fae55fff65bcb5908a43e63b5 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 30 Dec 2025 13:29:04 +0000 Subject: [PATCH 013/204] Search: Set limits on the amount of search terms Sets some reasonable limits, which are higher when logged in since that infers a little extra trust. Helps prevent against large resource consuption attacks via super heavy search queries. Thanks to Gabriel Rodrigues AKA TEXUGO for reporting. --- app/Search/SearchController.php | 5 +-- app/Search/SearchOptionSet.php | 8 +++++ app/Search/SearchOptions.php | 22 ++++++++++++++ tests/Search/SearchOptionsTest.php | 49 ++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/app/Search/SearchController.php b/app/Search/SearchController.php index 8a6a5bbdedf..348d44a427f 100644 --- a/app/Search/SearchController.php +++ b/app/Search/SearchController.php @@ -78,8 +78,9 @@ public function searchForSelector(Request $request, QueryPopular $queryPopular) // Search for entities otherwise show most popular if ($searchTerm !== false) { - $searchTerm .= ' {type:' . implode('|', $entityTypes) . '}'; - $entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20)['results']; + $options = SearchOptions::fromString($searchTerm); + $options->setFilter('type', implode('|', $entityTypes)); + $entities = $this->searchRunner->searchEntities($options, 'all', 1, 20)['results']; } else { $entities = $queryPopular->run(20, 0, $entityTypes); } diff --git a/app/Search/SearchOptionSet.php b/app/Search/SearchOptionSet.php index 844d145e6fc..19f1c550976 100644 --- a/app/Search/SearchOptionSet.php +++ b/app/Search/SearchOptionSet.php @@ -82,4 +82,12 @@ public function nonNegated(): self $values = array_values(array_filter($this->options, fn (SearchOption $option) => !$option->negated)); return new self($values); } + + /** + * @return self + */ + public function limit(int $limit): self + { + return new self(array_slice(array_values($this->options), 0, $limit)); + } } diff --git a/app/Search/SearchOptions.php b/app/Search/SearchOptions.php index bf527d9c305..83af2d043d8 100644 --- a/app/Search/SearchOptions.php +++ b/app/Search/SearchOptions.php @@ -35,6 +35,7 @@ public static function fromString(string $search): self { $instance = new self(); $instance->addOptionsFromString($search); + $instance->limitOptions(); return $instance; } @@ -87,6 +88,8 @@ public static function fromRequest(Request $request): self $instance->filters = $instance->filters->merge($extras->filters); } + $instance->limitOptions(); + return $instance; } @@ -147,6 +150,25 @@ protected function addOptionsFromString(string $searchString): void $this->filters = $this->filters->merge(new SearchOptionSet($terms['filters'])); } + /** + * Limit the amount of search options to reasonable levels. + * Provides higher limits to logged-in users since that signals a slightly + * higher level of trust. + */ + protected function limitOptions(): void + { + $userLoggedIn = !user()->isGuest(); + $searchLimit = $userLoggedIn ? 10 : 5; + $exactLimit = $userLoggedIn ? 4 : 2; + $tagLimit = $userLoggedIn ? 8 : 4; + $filterLimit = $userLoggedIn ? 10 : 5; + + $this->searches = $this->searches->limit($searchLimit); + $this->exacts = $this->exacts->limit($exactLimit); + $this->tags = $this->tags->limit($tagLimit); + $this->filters = $this->filters->limit($filterLimit); + } + /** * Decode backslash escaping within the input string. */ diff --git a/tests/Search/SearchOptionsTest.php b/tests/Search/SearchOptionsTest.php index 2ebf273dd55..4b0fa0f3aa4 100644 --- a/tests/Search/SearchOptionsTest.php +++ b/tests/Search/SearchOptionsTest.php @@ -142,4 +142,53 @@ public function test_from_request_properly_parses_out_extras_as_string() $this->assertEquals('dino', $options->exacts->all()[0]->value); $this->assertTrue($options->exacts->all()[0]->negated); } + + public function test_from_string_results_are_count_limited_and_larger_for_logged_in_users() + { + $terms = [ + ...array_fill(0, 40, 'cat'), + ...array_fill(0, 50, '"bees"'), + ...array_fill(0, 50, '{is_template}'), + ...array_fill(0, 50, '[a=b]'), + ]; + + $options = SearchOptions::fromString(implode(' ', $terms)); + + $this->assertCount(5, $options->searches->all()); + $this->assertCount(2, $options->exacts->all()); + $this->assertCount(4, $options->tags->all()); + $this->assertCount(5, $options->filters->all()); + + $this->asEditor(); + $options = SearchOptions::fromString(implode(' ', $terms)); + + $this->assertCount(10, $options->searches->all()); + $this->assertCount(4, $options->exacts->all()); + $this->assertCount(8, $options->tags->all()); + $this->assertCount(10, $options->filters->all()); + } + + public function test_from_request_results_are_count_limited_and_larger_for_logged_in_users() + { + $request = new Request([ + 'search' => str_repeat('hello ', 20), + 'tags' => array_fill(0, 20, 'a=b'), + 'extras' => str_repeat('-[b=c] -{viewed_by_me} -"dino"', 20), + ]); + + $options = SearchOptions::fromRequest($request); + + $this->assertCount(5, $options->searches->all()); + $this->assertCount(2, $options->exacts->all()); + $this->assertCount(4, $options->tags->all()); + $this->assertCount(5, $options->filters->all()); + + $this->asEditor(); + $options = SearchOptions::fromRequest($request); + + $this->assertCount(10, $options->searches->all()); + $this->assertCount(4, $options->exacts->all()); + $this->assertCount(8, $options->tags->all()); + $this->assertCount(10, $options->filters->all()); + } } From 082befb2fc89ec64fddc3476446b231f6fc133fc Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 30 Dec 2025 16:16:39 +0000 Subject: [PATCH 014/204] Updated PHP packages and translators pre v25.12.1 --- .github/translators.txt | 2 ++ composer.lock | 64 ++++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/.github/translators.txt b/.github/translators.txt index 61a6697fcf6..b69770939f8 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -519,3 +519,5 @@ Tahsin Ahmed (tahsinahmed2012) :: Bengali bojan_che :: Serbian (Cyrillic) setiawan setiawan (culture.setiawan) :: Indonesian Donald Mac Kenzie (kiuman) :: Norwegian Bokmal +Gabriel Silver (GabrielBSilver) :: Hebrew +Tomas Darius Davainis (Tomasdd) :: Lithuanian diff --git a/composer.lock b/composer.lock index 93bf172c6ec..06ef01bdd29 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.369.2", + "version": "3.369.4", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "5e3f541e344d71f3b9591fe1d94d9576530fa795" + "reference": "2aa1ef195e90140d733382e4341732ce113024f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5e3f541e344d71f3b9591fe1d94d9576530fa795", - "reference": "5e3f541e344d71f3b9591fe1d94d9576530fa795", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2aa1ef195e90140d733382e4341732ce113024f5", + "reference": "2aa1ef195e90140d733382e4341732ce113024f5", "shasum": "" }, "require": { @@ -85,7 +85,7 @@ "mtdowling/jmespath.php": "^2.8.0", "php": ">=8.1", "psr/http-message": "^1.0 || ^2.0", - "symfony/filesystem": "^v6.4.3 || ^v7.1.0 || ^v8.0.0" + "symfony/filesystem": "^v5.4.45 || ^v6.4.3 || ^v7.1.0 || ^v8.0.0" }, "require-dev": { "andrewsville/php-token-reflection": "^1.4", @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.369.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.369.4" }, - "time": "2025-12-23T19:21:43+00:00" + "time": "2025-12-29T19:07:47+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1055,24 +1055,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.1.3", + "version": "v1.1.4", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3" + "phpoption/phpoption": "^1.9.5" }, "require-dev": { - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" }, "type": "library", "autoload": { @@ -1101,7 +1101,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" }, "funding": [ { @@ -1113,7 +1113,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:45:45+00:00" + "time": "2025-12-27T19:43:20+00:00" }, { "name": "guzzlehttp/guzzle", @@ -3886,16 +3886,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.4", + "version": "1.9.5", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", - "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", "shasum": "" }, "require": { @@ -3945,7 +3945,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" }, "funding": [ { @@ -3957,7 +3957,7 @@ "type": "tidelift" } ], - "time": "2025-08-21T11:53:16+00:00" + "time": "2025-12-27T19:41:33+00:00" }, { "name": "phpseclib/phpseclib", @@ -7977,26 +7977,26 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.2", + "version": "v5.6.3", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" + "reference": "955e7815d677a3eaa7075231212f2110983adecc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", - "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", + "reference": "955e7815d677a3eaa7075231212f2110983adecc", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.3", + "graham-campbell/result-type": "^1.1.4", "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3", - "symfony/polyfill-ctype": "^1.24", - "symfony/polyfill-mbstring": "^1.24", - "symfony/polyfill-php80": "^1.24" + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", @@ -8045,7 +8045,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" }, "funding": [ { @@ -8057,7 +8057,7 @@ "type": "tidelift" } ], - "time": "2025-04-30T23:37:27+00:00" + "time": "2025-12-27T19:49:13+00:00" }, { "name": "voku/portable-ascii", From ab436ed5c3d2fb615d87d648c5dd6d779dbe26e2 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 30 Dec 2025 16:32:21 +0000 Subject: [PATCH 015/204] Updated translations with latest Crowdin changes (#5962) --- lang/ar/errors.php | 1 + lang/ar/validation.php | 1 + lang/bg/errors.php | 1 + lang/bg/validation.php | 1 + lang/bn/errors.php | 1 + lang/bn/validation.php | 1 + lang/bs/errors.php | 1 + lang/bs/validation.php | 1 + lang/ca/errors.php | 1 + lang/ca/validation.php | 1 + lang/cs/entities.php | 8 ++++---- lang/cs/errors.php | 1 + lang/cs/notifications.php | 4 ++-- lang/cs/preferences.php | 2 +- lang/cs/settings.php | 12 ++++++------ lang/cs/validation.php | 1 + lang/cy/errors.php | 1 + lang/cy/validation.php | 1 + lang/da/errors.php | 1 + lang/da/validation.php | 1 + lang/de/errors.php | 1 + lang/de/validation.php | 1 + lang/de_informal/errors.php | 1 + lang/de_informal/validation.php | 1 + lang/el/errors.php | 1 + lang/el/validation.php | 1 + lang/es/errors.php | 1 + lang/es/validation.php | 1 + lang/es_AR/errors.php | 1 + lang/es_AR/validation.php | 1 + lang/et/errors.php | 1 + lang/et/validation.php | 1 + lang/eu/errors.php | 1 + lang/eu/validation.php | 1 + lang/fa/errors.php | 1 + lang/fa/validation.php | 1 + lang/fi/errors.php | 1 + lang/fi/validation.php | 1 + lang/fr/errors.php | 1 + lang/fr/validation.php | 1 + lang/he/activities.php | 12 ++++++------ lang/he/auth.php | 5 +++-- lang/he/common.php | 4 ++-- lang/he/entities.php | 4 ++-- lang/he/errors.php | 1 + lang/he/validation.php | 1 + lang/hr/errors.php | 1 + lang/hr/validation.php | 1 + lang/hu/errors.php | 1 + lang/hu/validation.php | 1 + lang/id/components.php | 4 ++-- lang/id/errors.php | 1 + lang/id/validation.php | 1 + lang/is/errors.php | 1 + lang/is/validation.php | 1 + lang/it/errors.php | 1 + lang/it/validation.php | 1 + lang/ja/errors.php | 1 + lang/ja/notifications.php | 4 ++-- lang/ja/preferences.php | 2 +- lang/ja/settings.php | 12 ++++++------ lang/ja/validation.php | 1 + lang/ka/errors.php | 1 + lang/ka/validation.php | 1 + lang/ko/errors.php | 1 + lang/ko/validation.php | 1 + lang/ku/errors.php | 1 + lang/ku/validation.php | 1 + lang/lt/editor.php | 18 +++++++++--------- lang/lt/entities.php | 4 ++-- lang/lt/errors.php | 5 +++-- lang/lt/notifications.php | 12 ++++++------ lang/lt/passwords.php | 4 ++-- lang/lt/preferences.php | 2 +- lang/lt/settings.php | 4 ++-- lang/lt/validation.php | 1 + lang/lv/errors.php | 1 + lang/lv/validation.php | 1 + lang/nb/errors.php | 1 + lang/nb/validation.php | 1 + lang/ne/errors.php | 1 + lang/ne/validation.php | 1 + lang/nl/errors.php | 1 + lang/nl/validation.php | 1 + lang/nn/errors.php | 1 + lang/nn/validation.php | 1 + lang/pl/errors.php | 1 + lang/pl/validation.php | 1 + lang/pt/errors.php | 1 + lang/pt/validation.php | 1 + lang/pt_BR/errors.php | 1 + lang/pt_BR/validation.php | 1 + lang/ro/errors.php | 1 + lang/ro/validation.php | 1 + lang/ru/errors.php | 1 + lang/ru/validation.php | 1 + lang/sk/errors.php | 1 + lang/sk/validation.php | 1 + lang/sl/errors.php | 1 + lang/sl/validation.php | 1 + lang/sq/errors.php | 1 + lang/sq/validation.php | 1 + lang/sr/errors.php | 1 + lang/sr/validation.php | 1 + lang/sv/errors.php | 1 + lang/sv/validation.php | 1 + lang/tk/errors.php | 1 + lang/tk/validation.php | 1 + lang/tr/errors.php | 1 + lang/tr/validation.php | 1 + lang/uk/errors.php | 1 + lang/uk/validation.php | 1 + lang/uz/errors.php | 1 + lang/uz/validation.php | 1 + lang/vi/errors.php | 1 + lang/vi/validation.php | 1 + lang/zh_CN/errors.php | 1 + lang/zh_CN/validation.php | 1 + lang/zh_TW/errors.php | 1 + lang/zh_TW/validation.php | 1 + 120 files changed, 163 insertions(+), 60 deletions(-) diff --git a/lang/ar/errors.php b/lang/ar/errors.php index 4c6325cb3a1..491f04c079a 100644 --- a/lang/ar/errors.php +++ b/lang/ar/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'لم أتمكن من قراءة المِلَفّ المضغوط -ZIP-.', 'import_zip_cant_decode_data' => 'لم نتمكن من العثور على محتوى المِلَفّ المضغوط data.json وفك تشفيره.', 'import_zip_no_data' => 'لا تتضمن بيانات المِلَفّ المضغوط أي محتوى متوقع للكتاب أو الفصل أو الصفحة.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'فشل التحقق من صحة استيراد المِلَفّ المضغوط بسبب الأخطاء التالية:', 'import_zip_failed_notification' => 'فشل استيراد المِلَفّ المضغوط.', 'import_perms_books' => 'أنت تفتقر إلى الصلاحيات المطلوبة لإنشاء الكتب.', diff --git a/lang/ar/validation.php b/lang/ar/validation.php index c27770fe3a9..813f622a307 100644 --- a/lang/ar/validation.php +++ b/lang/ar/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'تعذر تحميل الملف. قد لا يقبل الخادم ملفات بهذا الحجم.', 'zip_file' => ':attribute بحاجة إلى الرجوع إلى مِلَفّ داخل المِلَفّ المضغوط.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute بحاجة إلى الإشارة إلى مِلَفّ من نوع :validTypes، وجدت :foundType.', 'zip_model_expected' => 'عنصر البيانات المتوقع ولكن ":type" تم العثور عليه.', 'zip_unique' => 'يجب أن يكون :attribute فريداً لنوع الكائن داخل المِلَفّ المضغوط.', diff --git a/lang/bg/errors.php b/lang/bg/errors.php index dd024518083..d8dbd4e11fb 100644 --- a/lang/bg/errors.php +++ b/lang/bg/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/bg/validation.php b/lang/bg/validation.php index e08eb55de61..e2f9bdaa9c1 100644 --- a/lang/bg/validation.php +++ b/lang/bg/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Файлът не можа да бъде качен. Сървърът може да не приема файлове с такъв размер.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/bn/errors.php b/lang/bn/errors.php index ee2fbfa2100..32dac63e2a8 100644 --- a/lang/bn/errors.php +++ b/lang/bn/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/bn/validation.php b/lang/bn/validation.php index d9b982d1e23..ff028525df3 100644 --- a/lang/bn/validation.php +++ b/lang/bn/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/bs/errors.php b/lang/bs/errors.php index f60f92f0782..fc1744805ca 100644 --- a/lang/bs/errors.php +++ b/lang/bs/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/bs/validation.php b/lang/bs/validation.php index 4b026afd2dc..e7e62f2abd4 100644 --- a/lang/bs/validation.php +++ b/lang/bs/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Fajl nije učitan. Server ne prihvata fajlove ove veličine.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/ca/errors.php b/lang/ca/errors.php index 6f3784c2cff..945d6fd0fa6 100644 --- a/lang/ca/errors.php +++ b/lang/ca/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'No es pot llegir el fitxer ZIP.', 'import_zip_cant_decode_data' => 'No s\'ha pogut trobar i descodificar el fitxer data.json en el fitxer ZIP.', 'import_zip_no_data' => 'Les dades del fitxer ZIP no contenen cap llibre, capítol o contingut de pàgina.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Error en validar la importació del ZIP amb els errors:', 'import_zip_failed_notification' => 'Error en importar l\'arxiu ZIP.', 'import_perms_books' => 'Li falten els permisos necessaris per crear llibres.', diff --git a/lang/ca/validation.php b/lang/ca/validation.php index bc821749a25..1debc5eb21d 100644 --- a/lang/ca/validation.php +++ b/lang/ca/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'No s’ha pogut pujar el fitxer. És possible que el servidor no admeti fitxers d’aquesta mida.', 'zip_file' => 'El :attribute necessita fer referència a un arxiu dins del ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'El :attribute necessita fer referència a un arxiu de tipus :validTyes, trobat :foundType.', 'zip_model_expected' => 'S\'esperava un objecte de dades, però s\'ha trobat ":type".', 'zip_unique' => 'El :attribute ha de ser únic pel tipus d\'objecte dins del ZIP.', diff --git a/lang/cs/entities.php b/lang/cs/entities.php index cda6b6c82f5..d65d85ccb7e 100644 --- a/lang/cs/entities.php +++ b/lang/cs/entities.php @@ -63,10 +63,10 @@ 'import_delete_desc' => 'Potvrzením odstraníte nahraný ZIP soubor. Tento krok nelze vrátit zpět.', 'import_errors' => 'Chyby importu', 'import_errors_desc' => 'Při pokusu o import došlo k následujícím chybám:', - 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', - 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter', - 'breadcrumb_siblings_for_book' => 'Navigate siblings for book', - 'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf', + 'breadcrumb_siblings_for_page' => 'Přejít na jinou stránku', + 'breadcrumb_siblings_for_chapter' => 'Přejít na jinou kapitolu', + 'breadcrumb_siblings_for_book' => 'Přejít na jinou knihu', + 'breadcrumb_siblings_for_bookshelf' => 'Přejít na jinou polici', // Permissions and restrictions 'permissions' => 'Oprávnění', diff --git a/lang/cs/errors.php b/lang/cs/errors.php index b1eeb54e015..2077bd4c40e 100644 --- a/lang/cs/errors.php +++ b/lang/cs/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Nelze načíst ZIP soubor.', 'import_zip_cant_decode_data' => 'Nelze najít a dekódovat data.json v archivu ZIP.', 'import_zip_no_data' => 'ZIP archiv neobsahuje knihy, kapitoly nebo stránky.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Importování ZIP selhalo s chybami:', 'import_zip_failed_notification' => 'Nepodařilo se naimportovat ZIP soubor.', 'import_perms_books' => 'Chybí vám požadovaná oprávnění k vytvoření knih.', diff --git a/lang/cs/notifications.php b/lang/cs/notifications.php index a6c9e88b562..7e20f654fd6 100644 --- a/lang/cs/notifications.php +++ b/lang/cs/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => 'Aktualizovaná stránka: :pageName', 'updated_page_intro' => 'V :appName byla aktualizována stránka:', 'updated_page_debounce' => 'Po nějakou dobu neobdržíte další oznámení o aktualizaci této stránky stejným editorem, aby se omezil počet stejných zpráv.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'Byli jste zmíněni v komentáři na stránce: :pageName', + 'comment_mention_intro' => 'Byli jste zmíněni v komentáři na webu :appName:', 'detail_page_name' => 'Název stránky:', 'detail_page_path' => 'Umístění:', diff --git a/lang/cs/preferences.php b/lang/cs/preferences.php index 4a9bf3ad71a..be0f972f9e0 100644 --- a/lang/cs/preferences.php +++ b/lang/cs/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'Nastavte si e-mailová oznámení, která dostanete při provedení určitých akcí v systému.', 'notifications_opt_own_page_changes' => 'Upozornit na změny stránek u kterých jsem vlastníkem', 'notifications_opt_own_page_comments' => 'Upozornit na komentáře na stránkách, které vlastním', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'Upozornit, když mě někdo zmíní v komentáři', 'notifications_opt_comment_replies' => 'Upozornit na odpovědi na mé komentáře', 'notifications_save' => 'Uložit nastavení', 'notifications_update_success' => 'Nastavení oznámení byla aktualizována!', diff --git a/lang/cs/settings.php b/lang/cs/settings.php index f856b64cfac..73ba6bfb077 100644 --- a/lang/cs/settings.php +++ b/lang/cs/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Žádná omezení nebyla nastavena', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', + 'sorting' => 'Seznamy a řazení', + 'sorting_book_default' => 'Výchozí řazení knih', 'sorting_book_default_desc' => 'Vybere výchozí pravidlo řazení pro nové knihy. Řazení neovlivní existující knihy a může být upraveno u konkrétní knihy.', 'sorting_rules' => 'Pravidla řazení', 'sorting_rules_desc' => 'Toto jsou předem definovaná pravidla řazení, která mohou být použita na webu.', @@ -103,8 +103,8 @@ 'sort_rule_op_updated_date' => 'Datum aktualizace', 'sort_rule_op_chapters_first' => 'Kapitoly jako první', 'sort_rule_op_chapters_last' => 'Kapitoly jako poslední', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits' => 'Počet zobrazených položek na stránce', + 'sorting_page_limits_desc' => 'Nastavte, kolik položek se má zobrazit na stránce v různých seznamech na webu. Obvykle bude nižší počet výkonnější, zatímco vyšší počet eliminuje nutnost proklikávat se několika stránkami. Doporučuje se použít sudý násobek čísla 3 (18, 24, 30 atd.).', // Maintenance settings 'maint' => 'Údržba', @@ -197,13 +197,13 @@ 'role_import_content' => 'Importovat obsah', 'role_editor_change' => 'Změnit editor stránek', 'role_notifications' => 'Přijímat a spravovat oznámení', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => 'Tato oprávnění zároveň umožní zobrazit a vyhledat uživatele a role na webu.', 'role_asset' => 'Obsahová oprávnění', 'roles_system_warning' => 'Berte na vědomí, že přístup k některému ze tří výše uvedených oprávnění může uživateli umožnit změnit svá vlastní oprávnění nebo oprávnění ostatních uživatelů v systému. Přiřazujte role s těmito oprávněními pouze důvěryhodným uživatelům.', 'role_asset_desc' => 'Tato oprávnění řídí přístup k obsahu napříč systémem. Specifická oprávnění na knihách, kapitolách a stránkách převáží tato nastavení.', 'role_asset_admins' => 'Administrátoři automaticky dostávají přístup k veškerému obsahu, ale tyto volby mohou ukázat nebo skrýt volby v uživatelském rozhraní.', 'role_asset_image_view_note' => 'To se týká viditelnosti ve správci obrázků. Skutečný přístup k nahraným souborům obrázků bude záviset na možnosti uložení systémových obrázků.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Tato oprávnění zároveň umožní zobrazit a vyhledat uživatele v systému.', 'role_all' => 'Vše', 'role_own' => 'Vlastní', 'role_controlled_by_asset' => 'Řídí se obsahem, do kterého jsou nahrávány', diff --git a/lang/cs/validation.php b/lang/cs/validation.php index b05d94625af..219f95a878f 100644 --- a/lang/cs/validation.php +++ b/lang/cs/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Nahrávání :attribute se nezdařilo.', 'zip_file' => ':attribute musí odkazovat na soubor v archivu ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute musí odkazovat na soubor typu :validTypes, nalezen :foundType.', 'zip_model_expected' => 'Očekáván datový objekt, ale nalezen „:type“.', 'zip_unique' => ':attribute musí být jedinečný pro typ objektu v archivu ZIP.', diff --git a/lang/cy/errors.php b/lang/cy/errors.php index db3a468b60a..f6123c9285a 100644 --- a/lang/cy/errors.php +++ b/lang/cy/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Wedi methu darllen ffeil ZIP.', 'import_zip_cant_decode_data' => 'Wedi methu ffeindio a dadgodio cynnwys ZIP data.json.', 'import_zip_no_data' => 'Nid oes cynnwys llyfr, pennod neu dudalen disgwyliedig yn nata ffeil ZIP.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'ZIP mewnforyn wedi\'i methu dilysu gyda gwallau:', 'import_zip_failed_notification' => 'Wedi methu mewnforio ffeil ZIP.', 'import_perms_books' => 'Dych chi\'n methu\'r caniatâd gofynnol i greu llyfrau.', diff --git a/lang/cy/validation.php b/lang/cy/validation.php index 5259f647244..63a19bf238d 100644 --- a/lang/cy/validation.php +++ b/lang/cy/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Nid oedd modd uwchlwytho’r ffeil. Efallai na fydd y gweinydd yn derbyn ffeiliau o\'r maint hwn.', 'zip_file' => 'Mae\'r :attribute angen cyfeirio at ffeil yn y ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'Mae\'r :attribute angen cyfeirio at ffeil o fath :valid Types, sydd wedi\'i ffeindio :foundType.', 'zip_model_expected' => 'Dyswgyl am wrthrych data ond wedi ffeindio ":type".', 'zip_unique' => 'Mae rhaid y :attribute fod yn unigol i\'r fath o wrthrych yn y ZIP.', diff --git a/lang/da/errors.php b/lang/da/errors.php index 864dec270dc..6f9d1d53cf2 100644 --- a/lang/da/errors.php +++ b/lang/da/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Kunne ikke læse ZIP-filen.', 'import_zip_cant_decode_data' => 'Kunne ikke finde og afkode ZIP data.json-indhold.', 'import_zip_no_data' => 'ZIP-filens data har ikke noget forventet bog-, kapitel- eller sideindhold.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP kunne ikke valideres med fejl:', 'import_zip_failed_notification' => 'Kunne ikke importere ZIP-fil.', 'import_perms_books' => 'Du mangler de nødvendige tilladelser til at oprette bøger.', diff --git a/lang/da/validation.php b/lang/da/validation.php index 3caf1417a45..36b9b49fb3f 100644 --- a/lang/da/validation.php +++ b/lang/da/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Filen kunne ikke oploades. Serveren accepterer muligvis ikke filer af denne størrelse.', 'zip_file' => 'Attributten skal henvise til en fil i ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'Attributten skal henvise til en fil af typen: validTypes, fundet:foundType.', 'zip_model_expected' => 'Data objekt forventet men ":type" fundet.', 'zip_unique' => 'Attributten skal være unik for objekttypen i ZIP.', diff --git a/lang/de/errors.php b/lang/de/errors.php index c1f65d94006..56dc6d59eb0 100644 --- a/lang/de/errors.php +++ b/lang/de/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'ZIP-Datei konnte nicht gelesen werden.', 'import_zip_cant_decode_data' => 'ZIP data.json konnte nicht gefunden und dekodiert werden.', 'import_zip_no_data' => 'ZIP-Datei Daten haben kein erwartetes Buch, Kapitel oder Seiteninhalt.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'ZIP Import konnte mit Fehlern nicht validiert werden:', 'import_zip_failed_notification' => 'Importieren der ZIP-Datei fehlgeschlagen.', 'import_perms_books' => 'Ihnen fehlt die erforderliche Berechtigung, um Bücher zu erstellen.', diff --git a/lang/de/validation.php b/lang/de/validation.php index 2ffad05298e..21e850bbf9c 100644 --- a/lang/de/validation.php +++ b/lang/de/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Die Datei konnte nicht hochgeladen werden. Der Server akzeptiert möglicherweise keine Dateien dieser Größe.', 'zip_file' => ':attribute muss eine Datei innerhalb des ZIP referenzieren.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute muss eine Datei des Typs :validType referenzieren, gefunden :foundType.', 'zip_model_expected' => 'Datenobjekt erwartet, aber ":type" gefunden.', 'zip_unique' => ':attribute muss für den Objekttyp innerhalb des ZIP eindeutig sein.', diff --git a/lang/de_informal/errors.php b/lang/de_informal/errors.php index 856f02b48c7..cd14dd92915 100644 --- a/lang/de_informal/errors.php +++ b/lang/de_informal/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'ZIP-Datei konnte nicht gelesen werden.', 'import_zip_cant_decode_data' => 'Konnte Inhalt der data.json im ZIP nicht finden und dekodieren.', 'import_zip_no_data' => 'ZIP-Datei hat kein erwartetes Buch, Kapitel oder Seiteninhalt.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'ZIP Import konnte aufgrund folgender Fehler nicht validiert werden:', 'import_zip_failed_notification' => 'Importieren der ZIP-Datei fehlgeschlagen.', 'import_perms_books' => 'Dir fehlt die erforderliche Berechtigung, um Bücher zu erstellen.', diff --git a/lang/de_informal/validation.php b/lang/de_informal/validation.php index f7be5a016c3..d693adecd83 100644 --- a/lang/de_informal/validation.php +++ b/lang/de_informal/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Die Datei konnte nicht hochgeladen werden. Der Server akzeptiert möglicherweise keine Dateien dieser Größe.', 'zip_file' => ':attribute muss auf eine Datei innerhalb des ZIP verweisen.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute muss eine Datei des Typs :validType referenzieren, gefunden :foundType.', 'zip_model_expected' => 'Datenobjekt erwartet, aber ":type" gefunden.', 'zip_unique' => ':attribute muss für den Objekttyp innerhalb des ZIP eindeutig sein.', diff --git a/lang/el/errors.php b/lang/el/errors.php index cdfa2155f13..fb13ec2fb05 100644 --- a/lang/el/errors.php +++ b/lang/el/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/el/validation.php b/lang/el/validation.php index 12d4919cab2..4f384efae64 100644 --- a/lang/el/validation.php +++ b/lang/el/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Δεν ήταν δυνατή η αποστολή του αρχείου. Ο διακομιστής ενδέχεται να μην δέχεται αρχεία αυτού του μεγέθους.', 'zip_file' => 'Το :attribute πρέπει να παραπέμπει σε ένα αρχείο εντός του ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'Το :attribute πρέπει να αναφέρεται σε αρχείο τύπου :validTypes, βρέθηκε :foundType.', 'zip_model_expected' => 'Αναμενόταν αντικείμενο δεδομένων, αλλά ":type" βρέθηκε.', 'zip_unique' => 'Το :attribute πρέπει να είναι μοναδικό για τον τύπο αντικειμένου εντός του ZIP.', diff --git a/lang/es/errors.php b/lang/es/errors.php index 98aa2acd593..53e035c0de2 100644 --- a/lang/es/errors.php +++ b/lang/es/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'No se pudo leer el archivo ZIP.', 'import_zip_cant_decode_data' => 'No se pudo encontrar y decodificar el archivo data.json. en el archivo ZIP.', 'import_zip_no_data' => 'Los datos del archivo ZIP no contienen ningún libro, capítulo o contenido de página.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Error al validar la importación del ZIP con errores:', 'import_zip_failed_notification' => 'Error al importar archivo ZIP.', 'import_perms_books' => 'Le faltan los permisos necesarios para crear libros.', diff --git a/lang/es/validation.php b/lang/es/validation.php index d5f4f8495c8..1a9aebd4cc9 100644 --- a/lang/es/validation.php +++ b/lang/es/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'El archivo no ha podido subirse. Es posible que el servidor no acepte archivos de este tamaño.', 'zip_file' => 'El :attribute necesita hacer referencia a un archivo dentro del ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'El :attribute necesita hacer referencia a un archivo de tipo :validTypes, encontrado :foundType.', 'zip_model_expected' => 'Se esperaba un objeto de datos, pero se encontró ":type".', 'zip_unique' => 'El :attribute debe ser único para el tipo de objeto dentro del ZIP.', diff --git a/lang/es_AR/errors.php b/lang/es_AR/errors.php index 16a5b5467e3..7bc3a189a0b 100644 --- a/lang/es_AR/errors.php +++ b/lang/es_AR/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'No se pudo leer el archivo ZIP.', 'import_zip_cant_decode_data' => 'No se pudo encontrar ni decodificar el contenido del archivo ZIP data.json.', 'import_zip_no_data' => 'Los datos del archivo ZIP no tienen un libro, un capítulo o contenido de página en su contenido.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Error al validar la importación del ZIP con los errores:', 'import_zip_failed_notification' => 'Error al importar archivo ZIP.', 'import_perms_books' => 'Le faltan los permisos necesarios para crear libros.', diff --git a/lang/es_AR/validation.php b/lang/es_AR/validation.php index db16845e030..1073933fe16 100644 --- a/lang/es_AR/validation.php +++ b/lang/es_AR/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'El archivo no se pudo subir. Puede ser que el servidor no acepte archivos de este tamaño.', 'zip_file' => 'El :attribute necesita hacer referencia a un archivo dentro del ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'El :attribute necesita hacer referencia a un archivo de tipo :validTypes, encontrado :foundType.', 'zip_model_expected' => 'Se esperaba un objeto de datos, pero se encontró ":type".', 'zip_unique' => 'El :attribute debe ser único para el tipo de objeto dentro del ZIP.', diff --git a/lang/et/errors.php b/lang/et/errors.php index 49e755fccf1..e1c95199386 100644 --- a/lang/et/errors.php +++ b/lang/et/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'ZIP-faili lugemine ebaõnnestus.', 'import_zip_cant_decode_data' => 'ZIP-failist ei leitud data.json sisu.', 'import_zip_no_data' => 'ZIP-failist ei leitud raamatute, peatükkide või lehtede sisu.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Imporditud ZIP-faili valideerimine ebaõnnestus vigadega:', 'import_zip_failed_notification' => 'ZIP-faili importimine ebaõnnestus.', 'import_perms_books' => 'Sul puuduvad õigused raamatute lisamiseks.', diff --git a/lang/et/validation.php b/lang/et/validation.php index 1354d1ed489..947007d2670 100644 --- a/lang/et/validation.php +++ b/lang/et/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Faili üleslaadimine ebaõnnestus. Server ei pruugi sellise suurusega faile vastu võtta.', 'zip_file' => ':attribute peab viitama failile ZIP-arhiivi sees.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute peab viitama :validTypes tüüpi failile, leiti :foundType.', 'zip_model_expected' => 'Oodatud andmete asemel leiti ":type".', 'zip_unique' => ':attribute peab olema ZIP-arhiivi piires objekti tüübile unikaalne.', diff --git a/lang/eu/errors.php b/lang/eu/errors.php index 2a747e5f8bb..822c6482944 100644 --- a/lang/eu/errors.php +++ b/lang/eu/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/eu/validation.php b/lang/eu/validation.php index f79dc852f3e..6dddd52975f 100644 --- a/lang/eu/validation.php +++ b/lang/eu/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/fa/errors.php b/lang/fa/errors.php index 9d5257fcf79..d357456159c 100644 --- a/lang/fa/errors.php +++ b/lang/fa/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'امکان ایجاد کاربر وجود ندارد؛ زیرا ارسال ایمیل دعوت با خطا مواجه شد.', 'import_zip_cant_decode_data' => 'محتوای data.json در فایل ZIP پیدا یا رمزگشایی نشد.', 'import_zip_no_data' => 'داده‌های فایل ZIP فاقد محتوای کتاب، فصل یا صفحه مورد انتظار است.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'اعتبارسنجی فایل ZIP واردشده با خطا مواجه شد:', 'import_zip_failed_notification' => ' فایل ZIP وارد نشد.', 'import_perms_books' => 'شما مجوز لازم برای ایجاد کتاب را ندارید.', diff --git a/lang/fa/validation.php b/lang/fa/validation.php index e1c69ece9a1..93c7dcb6669 100644 --- a/lang/fa/validation.php +++ b/lang/fa/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'بارگذاری فایل :attribute موفقیت آمیز نبود.', 'zip_file' => 'ویژگی :attribute باید به یک فایل درون پرونده فشرده شده اشاره کند.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'ویژگی :attribute باید به فایلی با نوع :validTypes اشاره کند، اما نوع یافت‌شده :foundType است.', 'zip_model_expected' => 'سیستم در این بخش انتظار دریافت یک شیء داده‌ای را داشت، اما «:type» دریافت گردید', 'zip_unique' => 'برای هر نوع شیء در فایل ZIP، مقدار ویژگی :attribute باید یکتا و بدون تکرار باشد.', diff --git a/lang/fi/errors.php b/lang/fi/errors.php index 9af7490d521..f470ce614c4 100644 --- a/lang/fi/errors.php +++ b/lang/fi/errors.php @@ -110,6 +110,7 @@ 'import_zip_cant_read' => 'ZIP-tiedostoa ei voitu lukea.', 'import_zip_cant_decode_data' => 'ZIP-tiedoston data.json sisältöä ei löydy eikä sitä voitu purkaa.', 'import_zip_no_data' => 'ZIP-tiedostoilla ei ole odotettua kirjaa, lukua tai sivun sisältöä.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Tuonti ZIP epäonnistui virheiden kanssa:', 'import_zip_failed_notification' => 'ZIP-tiedoston tuominen epäonnistui.', 'import_perms_books' => 'Sinulla ei ole tarvittavia oikeuksia luoda kirjoja.', diff --git a/lang/fi/validation.php b/lang/fi/validation.php index 8adc934a177..aef10b4d3ab 100644 --- a/lang/fi/validation.php +++ b/lang/fi/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Tiedostoa ei voitu ladata. Palvelin ei ehkä hyväksy tämän kokoisia tiedostoja.', 'zip_file' => 'Attribuutin :attribute on viitattava tiedostoon ZIP-tiedoston sisällä.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/fr/errors.php b/lang/fr/errors.php index 94d21e1dd14..a4fb9b565bc 100644 --- a/lang/fr/errors.php +++ b/lang/fr/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Impossible de lire le fichier ZIP.', 'import_zip_cant_decode_data' => 'Impossible de trouver et de décoder le contenu ZIP data.json.', 'import_zip_no_data' => 'Les données du fichier ZIP n\'ont pas de livre, de chapitre ou de page attendus.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'L\'importation du ZIP n\'a pas été validée avec les erreurs :', 'import_zip_failed_notification' => 'Impossible d\'importer le fichier ZIP.', 'import_perms_books' => 'Vous n\'avez pas les permissions requises pour créer des livres.', diff --git a/lang/fr/validation.php b/lang/fr/validation.php index 7db0493a165..74ecca12e38 100644 --- a/lang/fr/validation.php +++ b/lang/fr/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Le fichier n\'a pas pu être envoyé. Le serveur peut ne pas accepter des fichiers de cette taille.', 'zip_file' => 'L\'attribut :attribute doit référencer un fichier dans le ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute doit référencer un fichier de type :validTypes, trouvé :foundType.', 'zip_model_expected' => 'Objet de données attendu, mais ":type" trouvé.', 'zip_unique' => 'L\'attribut :attribute doit être unique pour le type d\'objet dans le ZIP.', diff --git a/lang/he/activities.php b/lang/he/activities.php index 8dd244b7247..e94d53e0d37 100644 --- a/lang/he/activities.php +++ b/lang/he/activities.php @@ -128,12 +128,12 @@ 'comment_delete' => 'תגובה נמחקה', // Sort Rules - 'sort_rule_create' => 'created sort rule', - 'sort_rule_create_notification' => 'Sort rule successfully created', - 'sort_rule_update' => 'updated sort rule', - 'sort_rule_update_notification' => 'Sort rule successfully updated', - 'sort_rule_delete' => 'deleted sort rule', - 'sort_rule_delete_notification' => 'Sort rule successfully deleted', + 'sort_rule_create' => 'נוצר חוק מיון', + 'sort_rule_create_notification' => 'חוק מיון נוצר בהצלחה', + 'sort_rule_update' => 'חוק מיון עודכן', + 'sort_rule_update_notification' => 'חוק מיון עודכן בהצלחה', + 'sort_rule_delete' => 'חוק מיון נמחק', + 'sort_rule_delete_notification' => 'חוק מיון נמחק בהצלחה', // Other 'permissions_update' => 'הרשאות עודכנו', diff --git a/lang/he/auth.php b/lang/he/auth.php index 2948d5b2078..6f4f77223cf 100644 --- a/lang/he/auth.php +++ b/lang/he/auth.php @@ -106,12 +106,13 @@ 'mfa_verify_access' => 'אשר גישה', 'mfa_verify_access_desc' => 'חשבון המשתמש שלך דורש ממך לאת את הזהות שלך בשכבת הגנה נוספת על מנת לאפשר לך גישה. יש לאשר גישה דרך אחד האמצעים הקיימים על מנת להמשיך.', 'mfa_verify_no_methods' => 'אין אפשרויות אימות דו-שלבי מוגדרות', - 'mfa_verify_no_methods_desc' => 'No multi-factor authentication methods could be found for your account. You\'ll need to set up at least one method before you gain access.', + 'mfa_verify_no_methods_desc' => 'לא נמצאו אפשרויות ווידוא זהות עבור המשתמש שלך. +נדרש לקנפג לפחות אחד על מנת לקבל גישה.', 'mfa_verify_use_totp' => 'אמת באמצעות אפליקציה', 'mfa_verify_use_backup_codes' => 'אמת באמצעות קוד גיבוי', 'mfa_verify_backup_code' => 'קוד גיבוי', 'mfa_verify_backup_code_desc' => 'הזן מטה אחד מקודי הגיבוי הנותרים לך:', 'mfa_verify_backup_code_enter_here' => 'הזן קוד גיבוי כאן', 'mfa_verify_totp_desc' => 'הזן את הקוד, שהונפק דרך האפליקציה שלך, מטה:', - 'mfa_setup_login_notification' => 'Multi-factor method configured, Please now login again using the configured method.', + 'mfa_setup_login_notification' => 'אמצעי זיהוי זהות הוגדרו, אנא התחבר מחדש.', ]; diff --git a/lang/he/common.php b/lang/he/common.php index 2e387f67d78..c89ee778214 100644 --- a/lang/he/common.php +++ b/lang/he/common.php @@ -30,8 +30,8 @@ 'create' => 'צור', 'update' => 'עדכן', 'edit' => 'ערוך', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'הכנס לארכיון', + 'unarchive' => 'הוצא מארכיון', 'sort' => 'מיין', 'move' => 'הזז', 'copy' => 'העתק', diff --git a/lang/he/entities.php b/lang/he/entities.php index 0cf2bb7fd32..d8b264d8b68 100644 --- a/lang/he/entities.php +++ b/lang/he/entities.php @@ -22,8 +22,8 @@ 'meta_created_name' => 'נוצר :timeLength על ידי :user', 'meta_updated' => 'עודכן :timeLength', 'meta_updated_name' => 'עודכן :timeLength על ידי :user', - 'meta_owned_name' => 'Owned by :user', - 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', + 'meta_owned_name' => 'בבעלות של :user', + 'meta_reference_count' => '', 'entity_select' => 'בחר יישות', 'entity_select_lack_permission' => 'אין לך אישורים דרושים לבחירת פריט זה', 'images' => 'תמונות', diff --git a/lang/he/errors.php b/lang/he/errors.php index 75f7949ea76..a2bc86ae492 100644 --- a/lang/he/errors.php +++ b/lang/he/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/he/validation.php b/lang/he/validation.php index 2d4f8b30598..db2080a2b1f 100644 --- a/lang/he/validation.php +++ b/lang/he/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'שדה :attribute ארעה שגיאה בעת ההעלאה.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/hr/errors.php b/lang/hr/errors.php index 92b9de7d24e..ad1b2668f53 100644 --- a/lang/hr/errors.php +++ b/lang/hr/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/hr/validation.php b/lang/hr/validation.php index 32b11a9bd80..22dae5c5604 100644 --- a/lang/hr/validation.php +++ b/lang/hr/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Datoteka se ne može prenijeti. Server možda ne prihvaća datoteke te veličine.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/hu/errors.php b/lang/hu/errors.php index 8ee055e29f7..10e3adbf9aa 100644 --- a/lang/hu/errors.php +++ b/lang/hu/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/hu/validation.php b/lang/hu/validation.php index a215416ca5f..b740ca0c60a 100644 --- a/lang/hu/validation.php +++ b/lang/hu/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'A fájlt nem lehet feltölteni. A kiszolgáló nem fogad el ilyen méretű fájlokat.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/id/components.php b/lang/id/components.php index 45aa72fba22..6f1f905a42a 100644 --- a/lang/id/components.php +++ b/lang/id/components.php @@ -13,7 +13,7 @@ 'image_intro_upload' => 'Unggah gambar baru dengan menyeret berkas gambar ke jendela ini, atau dengan menggunakan tombol "Unggah Gambar" di atas.', 'image_all' => 'Semua', 'image_all_title' => 'Lihat semua gambar', - 'image_book_title' => 'Lihat gambar yang diunggah ke buku ini', + 'image_book_title' => 'Lihat gambar untuk diunggah ke buku ini', 'image_page_title' => 'Lihat gambar yang diunggah ke halaman ini', 'image_search_hint' => 'Cari berdasarkan nama gambar', 'image_uploaded' => 'Diunggah :uploadedDate', @@ -33,7 +33,7 @@ 'image_update_success' => 'Detail gambar berhasil diperbarui', 'image_delete_success' => 'Gambar berhasil dihapus', 'image_replace' => 'Ganti Gambar', - 'image_replace_success' => 'Berkas gambar berhasil diperbarui', + 'image_replace_success' => 'Detail gambar berhasil diperbarui', 'image_rebuild_thumbs' => 'Buat Ulang Variasi Ukuran', 'image_rebuild_thumbs_success' => 'Variasi ukuran gambar berhasil dibuat ulang!', diff --git a/lang/id/errors.php b/lang/id/errors.php index 6f766173874..77254783108 100644 --- a/lang/id/errors.php +++ b/lang/id/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Tidak dapat membaca berkas ZIP.', 'import_zip_cant_decode_data' => 'Tidak dapat menemukan dan mendekode konten ZIP data.json.', 'import_zip_no_data' => 'Data berkas ZIP tidak berisi konten buku, bab, atau halaman yang diharapkan.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Impor ZIP gagal divalidasi dengan kesalahan:', 'import_zip_failed_notification' => 'Gagal mengimpor berkas ZIP.', 'import_perms_books' => 'Anda tidak memiliki izin yang diperlukan untuk membuat buku.', diff --git a/lang/id/validation.php b/lang/id/validation.php index d5ccb27093c..b649ef6feb4 100644 --- a/lang/id/validation.php +++ b/lang/id/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Berkas tidak dapat diunggah. Server mungkin tidak menerima berkas dengan ukuran ini.', 'zip_file' => ':attribute perlu merujuk ke sebuah file yang terdapat di dalam arsip ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute seharusnya berupa file dengan tipe :validTypes, tapi yang Anda unggah bertipe :foundType.', 'zip_model_expected' => 'Diharapkan sebuah objek data, namun yang ditemukan adalah \':type\'.', 'zip_unique' => ':attribute harus bersifat unik untuk setiap jenis objek dalam file ZIP.', diff --git a/lang/is/errors.php b/lang/is/errors.php index 50e30a8c57a..a29c8475a7b 100644 --- a/lang/is/errors.php +++ b/lang/is/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Gat ekki lesið ZIP skrá.', 'import_zip_cant_decode_data' => 'Fann ekki ZIP data.json innihald.', 'import_zip_no_data' => 'ZIP skráin inniheldur ekkert efni.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'ZIP skráin stóðst ekki staðfestingu og skilaði villu:', 'import_zip_failed_notification' => 'Gat ekki lesið inn ZIP skrá.', 'import_perms_books' => 'Þú hefur ekki heimild til að búa til bækur.', diff --git a/lang/is/validation.php b/lang/is/validation.php index 9183d27cf53..7f2af0f0810 100644 --- a/lang/is/validation.php +++ b/lang/is/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/it/errors.php b/lang/it/errors.php index b3763cce252..62b698accfb 100644 --- a/lang/it/errors.php +++ b/lang/it/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Impossibile leggere il file ZIP.', 'import_zip_cant_decode_data' => 'Impossibile trovare e decodificare il contenuto ZIP data.json.', 'import_zip_no_data' => 'I dati del file ZIP non hanno il contenuto previsto di libri, capitoli o pagine.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'L\'importazione ZIP non è stata convalidata con errori:', 'import_zip_failed_notification' => 'Impossibile importare il file ZIP.', 'import_perms_books' => 'Non hai i permessi necessari per creare libri.', diff --git a/lang/it/validation.php b/lang/it/validation.php index c945ff7d426..54ef4dba68f 100644 --- a/lang/it/validation.php +++ b/lang/it/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Il file non può essere caricato. Il server potrebbe non accettare file di questa dimensione.', 'zip_file' => 'L\'attributo :attribute deve fare riferimento a un file all\'interno dello ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'Il campo :attribute deve fare riferimento a un file di tipo :validTypes, trovato :foundType.', 'zip_model_expected' => 'Oggetto dati atteso ma ":type" trovato.', 'zip_unique' => 'L\'attributo :attribute deve essere univoco per il tipo di oggetto all\'interno dello ZIP.', diff --git a/lang/ja/errors.php b/lang/ja/errors.php index 8161c08b860..443a810f939 100644 --- a/lang/ja/errors.php +++ b/lang/ja/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'ZIPファイルを読み込めません。', 'import_zip_cant_decode_data' => 'ZIPファイル内に data.json が見つからないかデコードできませんでした。', 'import_zip_no_data' => 'ZIPファイルのデータにブック、チャプター、またはページコンテンツがありません。', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'エラーによりインポートZIPの検証に失敗しました:', 'import_zip_failed_notification' => 'ZIP ファイルのインポートに失敗しました。', 'import_perms_books' => 'ブックを作成するために必要な権限がありません。', diff --git a/lang/ja/notifications.php b/lang/ja/notifications.php index 98193217898..ea6286b513f 100644 --- a/lang/ja/notifications.php +++ b/lang/ja/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => 'ページの更新: :pageName', 'updated_page_intro' => ':appName でページが更新されました', 'updated_page_debounce' => '大量の通知を防ぐために、しばらくの間は同じユーザがこのページをさらに編集しても通知は送信されません。', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'ページのコメントであなたにメンションされています: :pageName', + 'comment_mention_intro' => ':appName: のコメントであなたにメンションされました', 'detail_page_name' => 'ページ名:', 'detail_page_path' => 'ページパス:', diff --git a/lang/ja/preferences.php b/lang/ja/preferences.php index a4790692953..c7451839372 100644 --- a/lang/ja/preferences.php +++ b/lang/ja/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'システム内で特定のアクティビティが実行されたときに受信する電子メール通知を制御します。', 'notifications_opt_own_page_changes' => '自分が所有するページの変更を通知する', 'notifications_opt_own_page_comments' => '自分が所有するページへのコメントを通知する', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'コメントでメンションされたときに通知する', 'notifications_opt_comment_replies' => '自分のコメントへの返信を通知する', 'notifications_save' => '設定を保存', 'notifications_update_success' => '通知設定を更新しました。', diff --git a/lang/ja/settings.php b/lang/ja/settings.php index a19c942b26d..53b14233e71 100644 --- a/lang/ja/settings.php +++ b/lang/ja/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => '制限しない', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', + 'sorting' => '一覧とソート', + 'sorting_book_default' => 'ブックのデフォルトソートルール', 'sorting_book_default_desc' => '新しいブックに適用するデフォルトのソートルールを選択します。これは既存のブックには影響しません。ルールはブックごとに上書きすることができます。', 'sorting_rules' => 'ソートルール', 'sorting_rules_desc' => 'これらはシステム内のコンテンツに適用できる事前定義のソート操作です。', @@ -103,8 +103,8 @@ 'sort_rule_op_updated_date' => '更新日時', 'sort_rule_op_chapters_first' => 'チャプタを最初に', 'sort_rule_op_chapters_last' => 'チャプタを最後に', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits' => 'ページング表示制限', + 'sorting_page_limits_desc' => 'システム内の各種リストで1ページに表示するアイテム数を設定します。 通常、少ない数に設定するとパフォーマンスが向上し、多い数に設定するとページの移動操作が少なくなります。 3の倍数(18、24、30など)を使用することをお勧めします。', // Maintenance settings 'maint' => 'メンテナンス', @@ -197,13 +197,13 @@ 'role_import_content' => 'コンテンツのインポート', 'role_editor_change' => 'ページエディタの変更', 'role_notifications' => '通知の受信と管理', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => '技術的には、これらの権限によりシステムのユーザーおよび役割の可視性と検索も提供されます。', 'role_asset' => 'アセット権限', 'roles_system_warning' => '上記の3つの権限のいずれかを付与することは、ユーザーが自分の特権またはシステム内の他のユーザーの特権を変更できる可能性があることに注意してください。これらの権限は信頼できるユーザーにのみ割り当ててください。', 'role_asset_desc' => '各アセットに対するデフォルトの権限を設定します。ここで設定した権限が優先されます。', 'role_asset_admins' => '管理者にはすべてのコンテンツへのアクセス権が自動的に付与されますが、これらのオプションはUIオプションを表示または非表示にする場合があります。', 'role_asset_image_view_note' => 'これは画像マネージャー内の可視性に関連しています。アップロードされた画像ファイルへの実際のアクセスは、システムの画像保存オプションに依存します。', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => '技術的には、これらの権限によりシステム内のユーザーの可視性と検索も提供されます。', 'role_all' => '全て', 'role_own' => '自身', 'role_controlled_by_asset' => 'このアセットに対し、右記の操作を許可:', diff --git a/lang/ja/validation.php b/lang/ja/validation.php index 7d18c85be72..0efbc7d682d 100644 --- a/lang/ja/validation.php +++ b/lang/ja/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'ファイルをアップロードできませんでした。サーバーがこのサイズのファイルを受け付けていない可能性があります。', 'zip_file' => ':attribute はZIP 内のファイルを参照する必要があります。', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute は種別 :validType のファイルを参照する必要がありますが、種別 :foundType となっています。', 'zip_model_expected' => 'データオブジェクトが期待されますが、":type" が見つかりました。', 'zip_unique' => 'ZIP内のオブジェクトタイプに :attribute が一意である必要があります。', diff --git a/lang/ka/errors.php b/lang/ka/errors.php index 9d738379648..77d7ee69e49 100644 --- a/lang/ka/errors.php +++ b/lang/ka/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/ka/validation.php b/lang/ka/validation.php index d9b982d1e23..ff028525df3 100644 --- a/lang/ka/validation.php +++ b/lang/ka/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/ko/errors.php b/lang/ko/errors.php index 9639a503613..12ee2697a41 100644 --- a/lang/ko/errors.php +++ b/lang/ko/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'ZIP 파일을 읽을 수 없습니다.', 'import_zip_cant_decode_data' => 'ZIP data.json 콘텐츠를 찾아서 디코딩할 수 없습니다.', 'import_zip_no_data' => '컨텐츠 ZIP 파일 데이터에 데이터가 비어있습니다.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => '컨텐츠 ZIP 파일을 가져오려다 실패했습니다. 이유:', 'import_zip_failed_notification' => '컨텐츠 ZIP 파일을 가져오지 못했습니다.', 'import_perms_books' => '책을 만드는 데 필요한 권한이 없습니다.', diff --git a/lang/ko/validation.php b/lang/ko/validation.php index ef7361ff9ac..a60ac2f213f 100644 --- a/lang/ko/validation.php +++ b/lang/ko/validation.php @@ -106,6 +106,7 @@ 'uploaded' => '파일 크기가 서버에서 허용하는 수치를 넘습니다.', 'zip_file' => ':attribute은(는) 컨텐츠 ZIP 파일 내의 객체 유형에 대해 고유해야 합니다.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute은(는) :validTypes, found :foundType 유형의 파일을 참조해야 합니다.', 'zip_model_expected' => '데이터 객체가 필요하지만 ":type" 타입이 발견되었습니다.', 'zip_unique' => ':attribute은(는) 컨텐츠 ZIP 파일 내의 객체 유형에 대해 고유해야 합니다.', diff --git a/lang/ku/errors.php b/lang/ku/errors.php index 9d738379648..77d7ee69e49 100644 --- a/lang/ku/errors.php +++ b/lang/ku/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/ku/validation.php b/lang/ku/validation.php index d9b982d1e23..ff028525df3 100644 --- a/lang/ku/validation.php +++ b/lang/ku/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/lt/editor.php b/lang/lt/editor.php index 0d250e9a7bd..0541908195c 100644 --- a/lang/lt/editor.php +++ b/lang/lt/editor.php @@ -37,7 +37,7 @@ 'blockquote' => 'Blockquote', 'inline_code' => 'Inline code', 'callouts' => 'Callouts', - 'callout_information' => 'Information', + 'callout_information' => 'Informacija', 'callout_success' => 'Success', 'callout_warning' => 'Warning', 'callout_danger' => 'Danger', @@ -61,7 +61,7 @@ 'list_task' => 'Task list', 'indent_increase' => 'Increase indent', 'indent_decrease' => 'Decrease indent', - 'table' => 'Table', + 'table' => 'Lentelė', 'insert_image' => 'Insert image', 'insert_image_title' => 'Insert/Edit Image', 'insert_link' => 'Insert/edit link', @@ -150,11 +150,11 @@ 'text_to_display' => 'Text to display', 'title' => 'Title', 'browse_links' => 'Browse links', - 'open_link' => 'Open link', - 'open_link_in' => 'Open link in...', + 'open_link' => 'Atverti nuorodą', + 'open_link_in' => 'Atverti nuorodą...', 'open_link_current' => 'Current window', - 'open_link_new' => 'New window', - 'remove_link' => 'Remove link', + 'open_link_new' => 'Naujame lange', + 'remove_link' => 'Pašalinti nuorodą', 'insert_collapsible' => 'Insert collapsible block', 'collapsible_unwrap' => 'Unwrap', 'edit_label' => 'Edit label', @@ -163,14 +163,14 @@ 'toggle_label' => 'Toggle label', // About view - 'about' => 'About the editor', - 'about_title' => 'About the WYSIWYG Editor', + 'about' => 'Apie redaktorių', + 'about_title' => 'Apie WYSIWYG redaktorių', 'editor_license' => 'Editor License & Copyright', 'editor_lexical_license' => 'This editor is built as a fork of :lexicalLink which is distributed under the MIT license.', 'editor_lexical_license_link' => 'Full license details can be found here.', 'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under the MIT license.', 'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.', - 'save_continue' => 'Save Page & Continue', + 'save_continue' => 'Išsaugoti puslapį ir tęsti', 'callouts_cycle' => '(Keep pressing to toggle through types)', 'link_selector' => 'Link to content', 'shortcuts' => 'Shortcuts', diff --git a/lang/lt/entities.php b/lang/lt/entities.php index 5624567811f..6c4472d85a7 100644 --- a/lang/lt/entities.php +++ b/lang/lt/entities.php @@ -416,7 +416,7 @@ 'comment_jump_to_thread' => 'Jump to thread', 'comment_delete_confirm' => 'Esate tikri, kad norite ištrinti šį komentarą?', 'comment_in_reply_to' => 'Atsakydamas į :commentId', - 'comment_reference' => 'Reference', + 'comment_reference' => 'Nuoroda', 'comment_reference_outdated' => '(Outdated)', 'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.', @@ -446,7 +446,7 @@ 'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?', // References - 'references' => 'References', + 'references' => 'Nuorodos', 'references_none' => 'There are no tracked references to this item.', 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', diff --git a/lang/lt/errors.php b/lang/lt/errors.php index 392e99a5192..f2917058eab 100644 --- a/lang/lt/errors.php +++ b/lang/lt/errors.php @@ -23,7 +23,7 @@ 'saml_no_email_address' => 'Nerandamas šio naudotojo elektroninio pašto adresas išorinės autentifikavimo sistemos pateiktuose duomenyse', 'saml_invalid_response_id' => 'Prašymas iš išorinės autentifikavimo sistemos nėra atpažintas proceso, kurį pradėjo ši programa. Naršymas po prisijungimo gali sukelti šią problemą.', 'saml_fail_authed' => 'Prisijungimas, naudojant :system nepavyko, sistema nepateikė sėkmingo leidimo.', - 'oidc_already_logged_in' => 'Already logged in', + 'oidc_already_logged_in' => 'Jau prisijungta', 'oidc_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system', 'oidc_fail_authed' => 'Login using :system failed, system did not provide successful authorization', 'social_no_action_defined' => 'Neapibrėžtas joks veiksmas', @@ -97,7 +97,7 @@ '404_page_not_found' => 'Puslapis nerastas', 'sorry_page_not_found' => 'Atleiskite, puslapis, kurio ieškote, nerastas.', 'sorry_page_not_found_permission_warning' => 'Jei tikėjotės, kad šis puslapis egzistuoja, galbūt neturite leidimo jo peržiūrėti.', - 'image_not_found' => 'Image Not Found', + 'image_not_found' => 'Paveikslėlis nerastas', 'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.', 'image_not_found_details' => 'If you expected this image to exist it might have been deleted.', 'return_home' => 'Grįžti į namus', @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/lt/notifications.php b/lang/lt/notifications.php index 563ac24e84d..5f938c3970b 100644 --- a/lang/lt/notifications.php +++ b/lang/lt/notifications.php @@ -6,7 +6,7 @@ 'new_comment_subject' => 'New comment on page: :pageName', 'new_comment_intro' => 'A user has commented on a page in :appName:', - 'new_page_subject' => 'New page: :pageName', + 'new_page_subject' => 'Naujas puslapis: :pageName', 'new_page_intro' => 'A new page has been created in :appName:', 'updated_page_subject' => 'Updated page: :pageName', 'updated_page_intro' => 'A page has been updated in :appName:', @@ -14,15 +14,15 @@ 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', - 'detail_page_name' => 'Page Name:', + 'detail_page_name' => 'Puslapio pavadinimas:', 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', - 'detail_comment' => 'Comment:', - 'detail_created_by' => 'Created By:', - 'detail_updated_by' => 'Updated By:', + 'detail_comment' => 'Komentaras:', + 'detail_created_by' => 'Sukurta:', + 'detail_updated_by' => 'Atnaujinta:', 'action_view_comment' => 'View Comment', - 'action_view_page' => 'View Page', + 'action_view_page' => 'Peržiūrėti puslapį', 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', 'footer_reason_link' => 'your notification preferences', diff --git a/lang/lt/passwords.php b/lang/lt/passwords.php index 672620d352b..e661c020fa3 100644 --- a/lang/lt/passwords.php +++ b/lang/lt/passwords.php @@ -7,9 +7,9 @@ return [ 'password' => 'Slaptažodis privalo būti mažiausiai aštuonių simbolių ir atitikti patvirtinimą.', - 'user' => "We can't find a user with that e-mail address.", + 'user' => "Nerastas vartotojas pagal šį el. pašto adresą.", 'token' => 'Slaptažodžio nustatymo raktas yra neteisingas šiam elektroninio pašto adresui.', - 'sent' => 'Elektroniu paštu jums atsiuntėme slaptažodžio atkūrimo nuorodą!', + 'sent' => 'Elektroniniu paštu Jums išsiųsta slaptažodžio atkūrimo nuoroda!', 'reset' => 'Jūsų slaptažodis buvo atkurtas!', ]; diff --git a/lang/lt/preferences.php b/lang/lt/preferences.php index 6258fcae754..7f591cdab57 100644 --- a/lang/lt/preferences.php +++ b/lang/lt/preferences.php @@ -25,7 +25,7 @@ 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', - 'notifications_save' => 'Save Preferences', + 'notifications_save' => 'Išsaugoti nuostatas', 'notifications_update_success' => 'Notification preferences have been updated!', 'notifications_watched' => 'Watched & Ignored Items', 'notifications_watched_desc' => 'Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', diff --git a/lang/lt/settings.php b/lang/lt/settings.php index 8120d85fe8e..bcc7c82bd70 100644 --- a/lang/lt/settings.php +++ b/lang/lt/settings.php @@ -9,8 +9,8 @@ // Common Messages 'settings' => 'Nustatymai', 'settings_save' => 'Išsaugoti nustatymus', - 'system_version' => 'System Version', - 'categories' => 'Categories', + 'system_version' => 'Sistemos versija', + 'categories' => 'Kategorijos', // App Settings 'app_customization' => 'Tinkinimas', diff --git a/lang/lt/validation.php b/lang/lt/validation.php index 92de23004aa..65e5936c2c4 100644 --- a/lang/lt/validation.php +++ b/lang/lt/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Šis failas negali būti įkeltas. Serveris gali nepriimti tokio dydžio failų.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/lv/errors.php b/lang/lv/errors.php index 28cc0d892c1..b4e9ec61aaf 100644 --- a/lang/lv/errors.php +++ b/lang/lv/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Nevarēja nolasīt ZIP failu.', 'import_zip_cant_decode_data' => 'Nevarēja atrast un nolasīt data.json saturu ZIP failā.', 'import_zip_no_data' => 'ZIP faila datos nav atrasts grāmatu, nodaļu vai lapu saturs.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'ZIP faila imports ir neveiksmīgs ar šādām kļūdām:', 'import_zip_failed_notification' => 'ZIP faila imports ir neveiksmīgs.', 'import_perms_books' => 'Jums nav nepieciešamo tiesību izveidot grāmatas.', diff --git a/lang/lv/validation.php b/lang/lv/validation.php index dd318119a58..befad5eeb9a 100644 --- a/lang/lv/validation.php +++ b/lang/lv/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Fails netika ielādēts. Serveris nevar pieņemt šāda izmēra failus.', 'zip_file' => ':attribute ir jāatsaucas uz failu ZIP arhīvā.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute ir jāatsaucas uz failu ar tipu :validTypes, bet atrasts :foundType.', 'zip_model_expected' => 'Sagaidīts datu objekts, bet atrasts ":type".', 'zip_unique' => ':attribute jābūt unikālam šim objekta tipam ZIP arhīvā.', diff --git a/lang/nb/errors.php b/lang/nb/errors.php index 400681b10d1..ef68da4fcbe 100644 --- a/lang/nb/errors.php +++ b/lang/nb/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Kunne ikke lese ZIP-filen.', 'import_zip_cant_decode_data' => 'Kunne ikke finne og dekode ZIP data.json innhold.', 'import_zip_no_data' => 'ZIP-fildata har ingen forventet bok, kapittel eller sideinnhold.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import av ZIP feilet i å validere med feil:', 'import_zip_failed_notification' => 'Kunne ikke importere ZIP-fil.', 'import_perms_books' => 'Du mangler nødvendige tillatelser for å lage bøker.', diff --git a/lang/nb/validation.php b/lang/nb/validation.php index a156da8f705..27922e4020e 100644 --- a/lang/nb/validation.php +++ b/lang/nb/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'kunne ikke lastes opp, tjeneren støtter ikke filer av denne størrelsen.', 'zip_file' => 'Attributtet :attribute må henvises til en fil i ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'Attributtet :attribute må referere en fil av typen :validTypes, som ble funnet :foundType.', 'zip_model_expected' => 'Data objekt forventet, men ":type" funnet.', 'zip_unique' => 'Attributtet :attribute må være unikt for objekttypen i ZIP.', diff --git a/lang/ne/errors.php b/lang/ne/errors.php index 79ee27c78d1..bbcaec9e180 100644 --- a/lang/ne/errors.php +++ b/lang/ne/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'ZIP फाइल पढ्न सकिएन।', 'import_zip_cant_decode_data' => 'ZIP डाटा.json सामग्री पत्ता लाग्न र डिकोड गर्न सकिएन।', 'import_zip_no_data' => 'ZIP फाइल डाटामा अपेक्षित पुस्तक, अध्याय वा पाना सामग्री छैन।', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'आयात ZIP प्रमाणीकरण असफल भयो। त्रुटिहरू छन्:', 'import_zip_failed_notification' => 'ZIP फाइल आयात गर्न असफल भयो।', 'import_perms_books' => 'तपाईंलाई पुस्तकहरू सिर्जना गर्न आवश्यक अनुमति छैन।', diff --git a/lang/ne/validation.php b/lang/ne/validation.php index f00c0f73153..ed61d3d6ff3 100644 --- a/lang/ne/validation.php +++ b/lang/ne/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'फाइल अपलोड हुन सकेन। सर्भरले यस्तो साइज स्वीकार नगर्न सक्छ।', 'zip_file' => ':attribute ले ZIP फाइलभित्रको फाइल देखाउनु पर्छ।', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute मा :validTypes प्रकारको फाइल हुनुपर्छ, तर :foundType भेटियो।', 'zip_model_expected' => 'डेटा वस्तु चाहिएको थियो तर ":type" भेटियो।', 'zip_unique' => ':attribute ZIP भित्रको वस्तु प्रकारको लागि अद्वितीय हुनुपर्छ।', diff --git a/lang/nl/errors.php b/lang/nl/errors.php index 9829986a2a7..c2a666546ef 100644 --- a/lang/nl/errors.php +++ b/lang/nl/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Kon het Zip-bestand niet lezen.', 'import_zip_cant_decode_data' => 'Kon de data.json Zip-inhoud niet vinden of decoderen.', 'import_zip_no_data' => 'Zip-bestand bevat niet de verwachte boek, hoofdstuk of pagina-inhoud.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'De validatie van het Zip-bestand is mislukt met de volgende fouten:', 'import_zip_failed_notification' => 'Importeren van het Zip-bestand is mislukt.', 'import_perms_books' => 'Je mist de vereiste machtigingen om boeken te maken.', diff --git a/lang/nl/validation.php b/lang/nl/validation.php index 7c7e20c8bfc..e2f48e31a63 100644 --- a/lang/nl/validation.php +++ b/lang/nl/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Het bestand kon niet worden geüpload. De server accepteert mogelijk geen bestanden van deze grootte.', 'zip_file' => 'Het \':attribute\' veld moet verwijzen naar een bestand in de ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'Het \':attribute\' veld moet verwijzen naar een bestand met het type :validTypes, vond :foundType.', 'zip_model_expected' => 'Dataobject verwacht maar vond ":type".', 'zip_unique' => ':attribute moet uniek zijn voor het objecttype binnen de ZIP.', diff --git a/lang/nn/errors.php b/lang/nn/errors.php index c56b7d07850..01d83e0ac01 100644 --- a/lang/nn/errors.php +++ b/lang/nn/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/nn/validation.php b/lang/nn/validation.php index a24ebd17189..ff7a026a2b5 100644 --- a/lang/nn/validation.php +++ b/lang/nn/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'kunne ikke lastes opp, tjeneren støtter ikke filer av denne størrelsen.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/pl/errors.php b/lang/pl/errors.php index e6ad2093f1f..04146bbb787 100644 --- a/lang/pl/errors.php +++ b/lang/pl/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/pl/validation.php b/lang/pl/validation.php index f20a66dfb6c..d1e8fada1df 100644 --- a/lang/pl/validation.php +++ b/lang/pl/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Plik nie może zostać wysłany. Serwer nie akceptuje plików o takim rozmiarze.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/pt/errors.php b/lang/pt/errors.php index b337005e14f..973bf61e2dc 100644 --- a/lang/pt/errors.php +++ b/lang/pt/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/pt/validation.php b/lang/pt/validation.php index df414c992e2..17d891cdb16 100644 --- a/lang/pt/validation.php +++ b/lang/pt/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'O arquivo não pôde ser carregado. O servidor pode não aceitar arquivos deste tamanho.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/pt_BR/errors.php b/lang/pt_BR/errors.php index 8dab893c799..37cbd7ff2c4 100644 --- a/lang/pt_BR/errors.php +++ b/lang/pt_BR/errors.php @@ -110,6 +110,7 @@ 'import_zip_cant_read' => 'Não foi possível ler o arquivo ZIP.', 'import_zip_cant_decode_data' => 'Não foi possível encontrar e decodificar o conteúdo ZIP data.json.', 'import_zip_no_data' => 'Os dados do arquivo ZIP não têm o conteúdo esperado livro, capítulo ou página.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Falhou na validação da importação do ZIP com erros:', 'import_zip_failed_notification' => 'Falhou ao importar arquivo ZIP.', 'import_perms_books' => 'Você não tem as permissões necessárias para criar livros.', diff --git a/lang/pt_BR/validation.php b/lang/pt_BR/validation.php index 9ddfdf2a566..e30ebc3778e 100644 --- a/lang/pt_BR/validation.php +++ b/lang/pt_BR/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'O arquivo não pôde ser carregado. O servidor pode não aceitar arquivos deste tamanho.', 'zip_file' => 'O :attribute precisa fazer referência a um arquivo do ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'O :attribute precisa fazer referência a um arquivo do tipo :validTypes, encontrado :foundType.', 'zip_model_expected' => 'Objeto de dados esperado, mas ":type" encontrado.', 'zip_unique' => 'O :attribute deve ser único para o tipo de objeto dentro do ZIP.', diff --git a/lang/ro/errors.php b/lang/ro/errors.php index 07c295b0785..dec7be134ad 100644 --- a/lang/ro/errors.php +++ b/lang/ro/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/ro/validation.php b/lang/ro/validation.php index 56a3e2e05d1..44fdd7ad864 100644 --- a/lang/ro/validation.php +++ b/lang/ro/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Fişierul nu a putut fi încărcat. Serverul nu poate accepta fişiere de această dimensiune.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/ru/errors.php b/lang/ru/errors.php index e87c9093629..82aecb657ae 100644 --- a/lang/ru/errors.php +++ b/lang/ru/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'У вас недостаточно прав для создания книг.', diff --git a/lang/ru/validation.php b/lang/ru/validation.php index 156a05fe678..ce94faa3b84 100644 --- a/lang/ru/validation.php +++ b/lang/ru/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Не удалось загрузить файл. Сервер не может принимать файлы такого размера.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/sk/errors.php b/lang/sk/errors.php index 143259f3ad4..30609b20146 100644 --- a/lang/sk/errors.php +++ b/lang/sk/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/sk/validation.php b/lang/sk/validation.php index 415f7cf05b9..60063752bf4 100644 --- a/lang/sk/validation.php +++ b/lang/sk/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Súbor sa nepodarilo nahrať. Server nemusí akceptovať súbory tejto veľkosti.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/sl/errors.php b/lang/sl/errors.php index da80fd3910e..cb3db274712 100644 --- a/lang/sl/errors.php +++ b/lang/sl/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/sl/validation.php b/lang/sl/validation.php index 0d9b56c1019..84ad6ca452d 100644 --- a/lang/sl/validation.php +++ b/lang/sl/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Datoteke ni bilo mogoče naložiti. Strežnik morda ne sprejema datotek te velikosti.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/sq/errors.php b/lang/sq/errors.php index 9d738379648..77d7ee69e49 100644 --- a/lang/sq/errors.php +++ b/lang/sq/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/sq/validation.php b/lang/sq/validation.php index d9b982d1e23..ff028525df3 100644 --- a/lang/sq/validation.php +++ b/lang/sq/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/sr/errors.php b/lang/sr/errors.php index ee8443461c3..f28fc01a94e 100644 --- a/lang/sr/errors.php +++ b/lang/sr/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/sr/validation.php b/lang/sr/validation.php index a2a617633ce..6770c5a8015 100644 --- a/lang/sr/validation.php +++ b/lang/sr/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/sv/errors.php b/lang/sv/errors.php index 4505dfa0b65..3a5478977c9 100644 --- a/lang/sv/errors.php +++ b/lang/sv/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Kunde inte läsa ZIP-filen.', 'import_zip_cant_decode_data' => 'Kunde inte hitta och avkoda ZIP data.json innehåll.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'ZIP-filen kunde inte valideras med fel:', 'import_zip_failed_notification' => 'Det gick inte att importera ZIP-fil.', 'import_perms_books' => 'Du saknar behörighet att skapa böcker.', diff --git a/lang/sv/validation.php b/lang/sv/validation.php index 4cc98c57598..b0c1f7a1bb9 100644 --- a/lang/sv/validation.php +++ b/lang/sv/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Filen kunde inte laddas upp. Servern kanske inte tillåter filer med denna storlek.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/tk/errors.php b/lang/tk/errors.php index 9d738379648..77d7ee69e49 100644 --- a/lang/tk/errors.php +++ b/lang/tk/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/tk/validation.php b/lang/tk/validation.php index d9b982d1e23..ff028525df3 100644 --- a/lang/tk/validation.php +++ b/lang/tk/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/tr/errors.php b/lang/tr/errors.php index fd742687127..259cc4b1c55 100644 --- a/lang/tr/errors.php +++ b/lang/tr/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/tr/validation.php b/lang/tr/validation.php index 9dbfadd6b9e..5916f454333 100644 --- a/lang/tr/validation.php +++ b/lang/tr/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Dosya yüklemesi başarısız oldu. Sunucu, bu boyuttaki dosyaları kabul etmiyor olabilir.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/uk/errors.php b/lang/uk/errors.php index eed61e8a027..40c0d90146d 100644 --- a/lang/uk/errors.php +++ b/lang/uk/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Не вдалося прочитати ZIP-файл.', 'import_zip_cant_decode_data' => 'Не вдалося знайти і розшифрувати контент ZIP data.json.', 'import_zip_no_data' => 'ZIP-файл не містить очікуваної книги, глави або вмісту сторінки.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Не вдалося виконати перевірку ZIP-адреси із помилками:', 'import_zip_failed_notification' => 'Не вдалося імпортувати ZIP-файл.', 'import_perms_books' => 'У Вас не вистачає необхідних прав для створення книг.', diff --git a/lang/uk/validation.php b/lang/uk/validation.php index 6161aba671a..79aa5070200 100644 --- a/lang/uk/validation.php +++ b/lang/uk/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Не вдалося завантажити файл. Сервер може не приймати файли такого розміру.', 'zip_file' => 'Поле :attribute повинне вказувати файл в ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'Поле :attribute повинне посилатись на файл типу :validtypes, знайдений :foundType.', 'zip_model_expected' => 'Очікувався об’єкт даних, але знайдено ":type".', 'zip_unique' => 'Поле :attribute має бути унікальним для типу об\'єкта в ZIP.', diff --git a/lang/uz/errors.php b/lang/uz/errors.php index a0d86c44191..052b29adf9a 100644 --- a/lang/uz/errors.php +++ b/lang/uz/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Could not read ZIP file.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Import ZIP failed to validate with errors:', 'import_zip_failed_notification' => 'Failed to import ZIP file.', 'import_perms_books' => 'You are lacking the required permissions to create books.', diff --git a/lang/uz/validation.php b/lang/uz/validation.php index d9d0fa0b2af..600195587c0 100644 --- a/lang/uz/validation.php +++ b/lang/uz/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Faylni yuklashda xatolik. Server bunday hajmdagi faylllarni yuklamasligi mumkin.', 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', 'zip_model_expected' => 'Data object expected but ":type" found.', 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', diff --git a/lang/vi/errors.php b/lang/vi/errors.php index bae87dc38cd..d422b716a0f 100644 --- a/lang/vi/errors.php +++ b/lang/vi/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => 'Không thể đọc tệp ZIP.', 'import_zip_cant_decode_data' => 'Không thể tìm và giải mã nội dung ZIP data.json.', 'import_zip_no_data' => 'Dữ liệu tệp ZIP không có nội dung sách, chương hoặc trang mong đợi.', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => 'Nhập tệp ZIP không hợp lệ với các lỗi:', 'import_zip_failed_notification' => 'Không thể nhập tệp ZIP.', 'import_perms_books' => 'Bạn không có quyền cần thiết để tạo sách.', diff --git a/lang/vi/validation.php b/lang/vi/validation.php index bc6fc0e39fd..d7d9a9de7f6 100644 --- a/lang/vi/validation.php +++ b/lang/vi/validation.php @@ -106,6 +106,7 @@ 'uploaded' => 'Tệp tin đã không được tải lên. Máy chủ không chấp nhận các tệp tin với dung lượng lớn như tệp tin trên.', 'zip_file' => ':attribute cần tham chiếu đến một tệp trong ZIP.', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute cần tham chiếu đến một tệp có kiểu: :validTypes, tìm thấy :foundType.', 'zip_model_expected' => 'Đối tượng dữ liệu được mong đợi nhưng tìm thấy ":type".', 'zip_unique' => ':attribute phải là duy nhất cho kiểu đối tượng trong ZIP.', diff --git a/lang/zh_CN/errors.php b/lang/zh_CN/errors.php index 37ef86af551..828e7c10203 100644 --- a/lang/zh_CN/errors.php +++ b/lang/zh_CN/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => '无法读取 ZIP 文件。', 'import_zip_cant_decode_data' => '无法找到并解码 ZIP data.json 内容。', 'import_zip_no_data' => 'ZIP 文件数据没有预期的书籍、章节或页面内容。', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => '导入 ZIP 验证失败,出现错误:', 'import_zip_failed_notification' => 'ZIP 文件导入失败。', 'import_perms_books' => '您缺少创建书籍所需的权限。', diff --git a/lang/zh_CN/validation.php b/lang/zh_CN/validation.php index 1a576e6bca4..748c8f56771 100644 --- a/lang/zh_CN/validation.php +++ b/lang/zh_CN/validation.php @@ -106,6 +106,7 @@ 'uploaded' => '无法上传文件。 服务器可能不接受此大小的文件。', 'zip_file' => ':attribute 需要引用 ZIP 内的文件。', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute 需要引用类型为 :validTypes 的文件,找到 :foundType 。', 'zip_model_expected' => '预期的数据对象,但找到了 ":type" 。', 'zip_unique' => '对于 ZIP 中的对象类型来说,:attribute 必须是唯一的。', diff --git a/lang/zh_TW/errors.php b/lang/zh_TW/errors.php index d4239ed6c64..e6ef7e020de 100644 --- a/lang/zh_TW/errors.php +++ b/lang/zh_TW/errors.php @@ -109,6 +109,7 @@ 'import_zip_cant_read' => '無法讀取 ZIP 檔案。', 'import_zip_cant_decode_data' => '無法尋找並解碼 ZIP data.json 內容。', 'import_zip_no_data' => 'ZIP 檔案資料沒有預期的書本、章節或頁面內容。', + 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', 'import_validation_failed' => '匯入 ZIP 驗證失敗,發生錯誤:', 'import_zip_failed_notification' => '匯入 ZIP 檔案失敗。', 'import_perms_books' => '您缺乏建立書本所需的權限。', diff --git a/lang/zh_TW/validation.php b/lang/zh_TW/validation.php index 30031470749..e6004c59d87 100644 --- a/lang/zh_TW/validation.php +++ b/lang/zh_TW/validation.php @@ -106,6 +106,7 @@ 'uploaded' => '無法上傳文檔案, 伺服器可能不接受此大小的檔案。', 'zip_file' => ':attribute 需要參照 ZIP 中的檔案。', + 'zip_file_size' => 'The file :attribute must not exceed :size MB.', 'zip_file_mime' => ':attribute 需要參照類型為 :validTypes 的檔案,找到 :foundType。', 'zip_model_expected' => '預期為資料物件,但找到「:type」。', 'zip_unique' => '對於 ZIP 中的物件類型,:attribute 必須是唯一的。', From 07ec880e3385221f99f3f1428fe5d8488887ff8d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 30 Dec 2025 17:09:26 +0000 Subject: [PATCH 016/204] Testing: Updated search tests to consider new limits --- tests/Search/SearchOptionsTest.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/Search/SearchOptionsTest.php b/tests/Search/SearchOptionsTest.php index 4b0fa0f3aa4..ffd20993560 100644 --- a/tests/Search/SearchOptionsTest.php +++ b/tests/Search/SearchOptionsTest.php @@ -35,9 +35,14 @@ public function test_from_string_parses_negations() public function test_from_string_properly_parses_escaped_quotes() { - $options = SearchOptions::fromString('"\"cat\"" surprise "\"\"" "\"donkey" "\"" "\\\\"'); + $options = SearchOptions::fromString('"\"cat\"" surprise'); + $this->assertEquals(['"cat"'], $options->exacts->toValueArray()); - $this->assertEquals(['"cat"', '""', '"donkey', '"', '\\'], $options->exacts->toValueArray()); + $options = SearchOptions::fromString('"\"\"" "\"donkey"'); + $this->assertEquals(['""', '"donkey'], $options->exacts->toValueArray()); + + $options = SearchOptions::fromString('"\"" "\\\\"'); + $this->assertEquals(['"', '\\'], $options->exacts->toValueArray()); } public function test_to_string_includes_all_items_in_the_correct_format() @@ -104,6 +109,7 @@ public function test_it_cannot_parse_out_empty_exacts() public function test_from_request_properly_parses_exacts_from_search_terms() { + $this->asEditor(); $request = new Request([ 'search' => 'biscuits "cheese" "" "baked beans"' ]); From 5c4fc3dc2c7241cc4d32cb9ea1bf4b8d890dce06 Mon Sep 17 00:00:00 2001 From: leon <505247370@qq.com> Date: Tue, 30 Dec 2025 18:11:22 +0800 Subject: [PATCH 017/204] fix: Docker: Add git safe.directory config for bind-mounted repos.Mark /app as safe directory to handle Git 2.35+ ownership checks in Docker containers. --- dev/docker/entrypoint.app.sh | 4 ++++ dev/docker/entrypoint.node.sh | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/dev/docker/entrypoint.app.sh b/dev/docker/entrypoint.app.sh index b09edda8863..5da7c247ccc 100755 --- a/dev/docker/entrypoint.app.sh +++ b/dev/docker/entrypoint.app.sh @@ -1,5 +1,9 @@ #!/bin/bash +# Git 2.35+ may refuse to operate on bind-mounted repos with differing ownership ("dubious ownership"). +# Mark /app as safe within the container. +git config --global --add safe.directory /app 2>/dev/null || true + set -e env diff --git a/dev/docker/entrypoint.node.sh b/dev/docker/entrypoint.node.sh index a8f33fd3d93..b8cc0d7fb2e 100755 --- a/dev/docker/entrypoint.node.sh +++ b/dev/docker/entrypoint.node.sh @@ -1,5 +1,9 @@ #!/bin/sh +# Git 2.35+ may refuse to operate on bind-mounted repos with differing ownership ("dubious ownership"). +# Mark /app as safe within the container. +git config --global --add safe.directory /app 2>/dev/null || true + set -e npm install From 018de5def3342f1f1bd62354ffa239ade2d937cd Mon Sep 17 00:00:00 2001 From: leon <505247370@qq.com> Date: Wed, 31 Dec 2025 13:46:26 +0800 Subject: [PATCH 018/204] fix: Configure safe directory for git in dockerfile --- dev/docker/Dockerfile | 3 +++ dev/docker/entrypoint.app.sh | 4 ---- dev/docker/entrypoint.node.sh | 4 ---- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/dev/docker/Dockerfile b/dev/docker/Dockerfile index edab90ca1c5..b64899f797f 100644 --- a/dev/docker/Dockerfile +++ b/dev/docker/Dockerfile @@ -14,6 +14,9 @@ RUN apt-get update && \ wait-for-it && \ rm -rf /var/lib/apt/lists/* +# Mark /app as safe for Git >= 2.35.2 +RUN git config --system --add safe.directory /app + # Install PHP extensions RUN docker-php-ext-configure ldap --with-libdir="lib/$(gcc -dumpmachine)" && \ docker-php-ext-configure gd --with-freetype --with-jpeg && \ diff --git a/dev/docker/entrypoint.app.sh b/dev/docker/entrypoint.app.sh index 5da7c247ccc..b09edda8863 100755 --- a/dev/docker/entrypoint.app.sh +++ b/dev/docker/entrypoint.app.sh @@ -1,9 +1,5 @@ #!/bin/bash -# Git 2.35+ may refuse to operate on bind-mounted repos with differing ownership ("dubious ownership"). -# Mark /app as safe within the container. -git config --global --add safe.directory /app 2>/dev/null || true - set -e env diff --git a/dev/docker/entrypoint.node.sh b/dev/docker/entrypoint.node.sh index b8cc0d7fb2e..a8f33fd3d93 100755 --- a/dev/docker/entrypoint.node.sh +++ b/dev/docker/entrypoint.node.sh @@ -1,9 +1,5 @@ #!/bin/sh -# Git 2.35+ may refuse to operate on bind-mounted repos with differing ownership ("dubious ownership"). -# Mark /app as safe within the container. -git config --global --add safe.directory /app 2>/dev/null || true - set -e npm install From 43eed1660c9da690983a6e4e130fbf099d9b005a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 7 Jan 2026 11:09:39 +0000 Subject: [PATCH 019/204] Meta: Updated dev version, license year, crowdin config Added Id to crowdin config for compatibility with upcoming change to crowdin CLI process after switch to codeberg --- LICENSE | 2 +- crowdin.yml | 1 + version | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 9b94d9be3f3..d7961a61319 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2025, Dan Brown and the BookStack project contributors. +Copyright (c) 2015-2026, Dan Brown and the BookStack project contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crowdin.yml b/crowdin.yml index 32f2ad05225..b803b07eea1 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,3 +1,4 @@ +project_id: "377219" project_identifier: bookstack base_path: . preserve_hierarchy: false diff --git a/version b/version index 085e9695f3d..14f310dc37f 100644 --- a/version +++ b/version @@ -1 +1 @@ -v25.11-dev +v26.01-dev From da7bedd2e4cfaa1bed4f2a4665a2ff89d2a1f86b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 13 Jan 2026 13:23:54 +0000 Subject: [PATCH 020/204] Sponsors: Added Onyx --- readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readme.md b/readme.md index 3ee5f242aeb..d134a24799a 100644 --- a/readme.md +++ b/readme.md @@ -56,6 +56,11 @@ Big thanks to these companies for supporting the project. Diagrams.net
    + + + onyx.app + + #### Bronze Sponsors From 19f02d927e8019ade8f126be81bd94e6a6022600 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 22 Jan 2026 17:39:26 +0000 Subject: [PATCH 021/204] Deps: Updated PHP package versions --- composer.lock | 485 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 316 insertions(+), 169 deletions(-) diff --git a/composer.lock b/composer.lock index 06ef01bdd29..7a57dcab392 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.369.4", + "version": "3.369.17", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "2aa1ef195e90140d733382e4341732ce113024f5" + "reference": "8bdccd2f8e54c5cd170b22f52414171e19226fd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2aa1ef195e90140d733382e4341732ce113024f5", - "reference": "2aa1ef195e90140d733382e4341732ce113024f5", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/8bdccd2f8e54c5cd170b22f52414171e19226fd1", + "reference": "8bdccd2f8e54c5cd170b22f52414171e19226fd1", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.369.4" + "source": "https://github.com/aws/aws-sdk-php/tree/3.369.17" }, - "time": "2025-12-29T19:07:47+00:00" + "time": "2026-01-21T19:09:32+00:00" }, { "name": "bacon/bacon-qr-code", @@ -699,16 +699,16 @@ }, { "name": "dompdf/php-font-lib", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/dompdf/php-font-lib.git", - "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d" + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", - "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a", "shasum": "" }, "require": { @@ -716,7 +716,7 @@ "php": "^7.1 || ^8.0" }, "require-dev": { - "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12" }, "type": "library", "autoload": { @@ -738,31 +738,31 @@ "homepage": "https://github.com/dompdf/php-font-lib", "support": { "issues": "https://github.com/dompdf/php-font-lib/issues", - "source": "https://github.com/dompdf/php-font-lib/tree/1.0.1" + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.2" }, - "time": "2024-12-02T14:37:59+00:00" + "time": "2026-01-20T14:10:26+00:00" }, { "name": "dompdf/php-svg-lib", - "version": "1.0.0", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/dompdf/php-svg-lib.git", - "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af" + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/eb045e518185298eb6ff8d80d0d0c6b17aecd9af", - "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^7.1 || ^8.0", - "sabberworm/php-css-parser": "^8.4" + "sabberworm/php-css-parser": "^8.4 || ^9.0" }, "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11" }, "type": "library", "autoload": { @@ -784,9 +784,9 @@ "homepage": "https://github.com/dompdf/php-svg-lib", "support": { "issues": "https://github.com/dompdf/php-svg-lib/issues", - "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.0" + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2" }, - "time": "2024-04-29T13:26:35+00:00" + "time": "2026-01-02T16:01:13+00:00" }, { "name": "dragonmantank/cron-expression", @@ -921,16 +921,16 @@ }, { "name": "firebase/php-jwt", - "version": "v6.11.1", + "version": "v7.0.2", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" + "reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/5645b43af647b6947daac1d0f659dd1fbe8d3b65", + "reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65", "shasum": "" }, "require": { @@ -978,9 +978,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" + "source": "https://github.com/firebase/php-jwt/tree/v7.0.2" }, - "time": "2025-04-09T20:32:01+00:00" + "time": "2025-12-16T22:17:28+00:00" }, { "name": "fruitcake/php-cors", @@ -1528,16 +1528,16 @@ }, { "name": "intervention/gif", - "version": "4.2.2", + "version": "4.2.4", "source": { "type": "git", "url": "https://github.com/Intervention/gif.git", - "reference": "5999eac6a39aa760fb803bc809e8909ee67b451a" + "reference": "c3598a16ebe7690cd55640c44144a9df383ea73c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/gif/zipball/5999eac6a39aa760fb803bc809e8909ee67b451a", - "reference": "5999eac6a39aa760fb803bc809e8909ee67b451a", + "url": "https://api.github.com/repos/Intervention/gif/zipball/c3598a16ebe7690cd55640c44144a9df383ea73c", + "reference": "c3598a16ebe7690cd55640c44144a9df383ea73c", "shasum": "" }, "require": { @@ -1576,7 +1576,7 @@ ], "support": { "issues": "https://github.com/Intervention/gif/issues", - "source": "https://github.com/Intervention/gif/tree/4.2.2" + "source": "https://github.com/Intervention/gif/tree/4.2.4" }, "funding": [ { @@ -1592,7 +1592,7 @@ "type": "ko_fi" } ], - "time": "2025-03-29T07:46:21+00:00" + "time": "2026-01-04T09:27:23+00:00" }, { "name": "intervention/image", @@ -1739,16 +1739,16 @@ }, { "name": "laravel/framework", - "version": "v12.44.0", + "version": "v12.48.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "592bbf1c036042958332eb98e3e8131b29102f33" + "reference": "0f0974a9769378ccd9c9935c09b9927f3a606830" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/592bbf1c036042958332eb98e3e8131b29102f33", - "reference": "592bbf1c036042958332eb98e3e8131b29102f33", + "url": "https://api.github.com/repos/laravel/framework/zipball/0f0974a9769378ccd9c9935c09b9927f3a606830", + "reference": "0f0974a9769378ccd9c9935c09b9927f3a606830", "shasum": "" }, "require": { @@ -1861,7 +1861,7 @@ "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", "opis/json-schema": "^2.4.1", - "orchestra/testbench-core": "^10.8.1", + "orchestra/testbench-core": "^10.9.0", "pda/pheanstalk": "^5.0.6|^7.0.0", "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", @@ -1957,20 +1957,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-12-23T15:29:43+00:00" + "time": "2026-01-20T16:12:36+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.8", + "version": "v0.3.10", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "096748cdfb81988f60090bbb839ce3205ace0d35" + "reference": "360ba095ef9f51017473505191fbd4ab73e1cab3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/096748cdfb81988f60090bbb839ce3205ace0d35", - "reference": "096748cdfb81988f60090bbb839ce3205ace0d35", + "url": "https://api.github.com/repos/laravel/prompts/zipball/360ba095ef9f51017473505191fbd4ab73e1cab3", + "reference": "360ba095ef9f51017473505191fbd4ab73e1cab3", "shasum": "" }, "require": { @@ -2014,22 +2014,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.8" + "source": "https://github.com/laravel/prompts/tree/v0.3.10" }, - "time": "2025-11-21T20:52:52+00:00" + "time": "2026-01-13T20:29:29+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.7", + "version": "v2.0.8", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd" + "reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/cb291e4c998ac50637c7eeb58189c14f5de5b9dd", - "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7581a4407012f5f53365e11bafc520fd7f36bc9b", + "reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b", "shasum": "" }, "require": { @@ -2077,25 +2077,25 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-11-21T20:52:36+00:00" + "time": "2026-01-08T16:22:46+00:00" }, { "name": "laravel/socialite", - "version": "v5.24.0", + "version": "v5.24.2", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "1d19358c28e8951dde6e36603b89d8f09e6cfbfd" + "reference": "5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/1d19358c28e8951dde6e36603b89d8f09e6cfbfd", - "reference": "1d19358c28e8951dde6e36603b89d8f09e6cfbfd", + "url": "https://api.github.com/repos/laravel/socialite/zipball/5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613", + "reference": "5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613", "shasum": "" }, "require": { "ext-json": "*", - "firebase/php-jwt": "^6.4", + "firebase/php-jwt": "^6.4|^7.0", "guzzlehttp/guzzle": "^6.0|^7.0", "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", @@ -2149,20 +2149,20 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2025-12-09T15:37:06+00:00" + "time": "2026-01-10T16:07:28+00:00" }, { "name": "laravel/tinker", - "version": "v2.10.2", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c" + "reference": "3d34b97c9a1747a81a3fde90482c092bd8b66468" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/3bcb5f62d6f837e0f093a601e26badafb127bd4c", - "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c", + "url": "https://api.github.com/repos/laravel/tinker/zipball/3d34b97c9a1747a81a3fde90482c092bd8b66468", + "reference": "3d34b97c9a1747a81a3fde90482c092bd8b66468", "shasum": "" }, "require": { @@ -2171,7 +2171,7 @@ "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", "php": "^7.2.5|^8.0", "psy/psysh": "^0.11.1|^0.12.0", - "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0" + "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0|^8.0" }, "require-dev": { "mockery/mockery": "~1.3.3|^1.4.2", @@ -2213,9 +2213,9 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.10.2" + "source": "https://github.com/laravel/tinker/tree/v2.11.0" }, - "time": "2025-11-20T16:29:12+00:00" + "time": "2025-12-19T19:16:45+00:00" }, { "name": "league/commonmark", @@ -2881,20 +2881,20 @@ }, { "name": "league/uri", - "version": "7.7.0", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807" + "reference": "4436c6ec8d458e4244448b069cc572d088230b76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/8d587cddee53490f9b82bf203d3a9aa7ea4f9807", - "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/4436c6ec8d458e4244448b069cc572d088230b76", + "reference": "4436c6ec8d458e4244448b069cc572d088230b76", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.7", + "league/uri-interfaces": "^7.8", "php": "^8.1", "psr/http-factory": "^1" }, @@ -2908,11 +2908,11 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "ext-uri": "to use the PHP native URI class", - "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", - "league/uri-components": "Needed to easily manipulate URI objects components", - "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -2967,7 +2967,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.7.0" + "source": "https://github.com/thephpleague/uri/tree/7.8.0" }, "funding": [ { @@ -2975,20 +2975,20 @@ "type": "github" } ], - "time": "2025-12-07T16:02:06+00:00" + "time": "2026-01-14T17:24:56+00:00" }, { "name": "league/uri-interfaces", - "version": "7.7.0", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c" + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/62ccc1a0435e1c54e10ee6022df28d6c04c2946c", - "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/c5c5cd056110fc8afaba29fa6b72a43ced42acd4", + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4", "shasum": "" }, "require": { @@ -3001,7 +3001,7 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -3051,7 +3051,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.7.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.0" }, "funding": [ { @@ -3059,7 +3059,7 @@ "type": "github" } ], - "time": "2025-12-07T16:03:21+00:00" + "time": "2026-01-15T06:54:53+00:00" }, { "name": "masterminds/html5", @@ -3130,16 +3130,16 @@ }, { "name": "monolog/monolog", - "version": "3.9.0", + "version": "3.10.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", - "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", "shasum": "" }, "require": { @@ -3157,7 +3157,7 @@ "graylog2/gelf-php": "^1.4.2 || ^2.0", "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.2", - "mongodb/mongodb": "^1.8", + "mongodb/mongodb": "^1.8 || ^2.0", "php-amqplib/php-amqplib": "~2.4 || ^3", "php-console/php-console": "^3.1.8", "phpstan/phpstan": "^2", @@ -3217,7 +3217,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.9.0" + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" }, "funding": [ { @@ -3229,7 +3229,7 @@ "type": "tidelift" } ], - "time": "2025-03-24T10:02:05+00:00" + "time": "2026-01-02T08:56:05+00:00" }, { "name": "mtdowling/jmespath.php", @@ -4917,25 +4917,33 @@ }, { "name": "sabberworm/php-css-parser", - "version": "v8.9.0", + "version": "v9.1.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9" + "reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d8e916507b88e389e26d4ab03c904a082aa66bb9", - "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb", + "reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb", "shasum": "" }, "require": { "ext-iconv": "*", - "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.3" }, "require-dev": { - "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41", - "rawr/cross-data-providers": "^2.0.0" + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "1.12.28 || 2.1.25", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.7", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6", + "phpunit/phpunit": "8.5.46", + "rawr/phpunit-data-provider": "3.3.1", + "rector/rector": "1.2.10 || 2.1.7", + "rector/type-perfect": "1.0.0 || 2.1.0" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" @@ -4943,7 +4951,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.0.x-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -4977,9 +4985,9 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.9.0" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.1.0" }, - "time": "2025-07-11T13:20:48+00:00" + "time": "2025-09-14T07:37:21+00:00" }, { "name": "socialiteproviders/discord", @@ -5429,16 +5437,16 @@ }, { "name": "symfony/console", - "version": "v7.4.1", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e" + "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e", - "reference": "6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e", + "url": "https://api.github.com/repos/symfony/console/zipball/732a9ca6cd9dfd940c639062d5edbde2f6727fb6", + "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6", "shasum": "" }, "require": { @@ -5503,7 +5511,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.1" + "source": "https://github.com/symfony/console/tree/v7.4.3" }, "funding": [ { @@ -5523,7 +5531,7 @@ "type": "tidelift" } ], - "time": "2025-12-05T15:23:39+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/css-selector", @@ -5976,16 +5984,16 @@ }, { "name": "symfony/finder", - "version": "v7.4.0", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd" + "reference": "fffe05569336549b20a1be64250b40516d6e8d06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/340b9ed7320570f319028a2cbec46d40535e94bd", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd", + "url": "https://api.github.com/repos/symfony/finder/zipball/fffe05569336549b20a1be64250b40516d6e8d06", + "reference": "fffe05569336549b20a1be64250b40516d6e8d06", "shasum": "" }, "require": { @@ -6020,7 +6028,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.0" + "source": "https://github.com/symfony/finder/tree/v7.4.3" }, "funding": [ { @@ -6040,20 +6048,20 @@ "type": "tidelift" } ], - "time": "2025-11-05T05:42:40+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.1", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "bd1af1e425811d6f077db240c3a588bdb405cd27" + "reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/bd1af1e425811d6f077db240c3a588bdb405cd27", - "reference": "bd1af1e425811d6f077db240c3a588bdb405cd27", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a70c745d4cea48dbd609f4075e5f5cbce453bd52", + "reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52", "shasum": "" }, "require": { @@ -6102,7 +6110,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.1" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.3" }, "funding": [ { @@ -6122,20 +6130,20 @@ "type": "tidelift" } ], - "time": "2025-12-07T11:13:10+00:00" + "time": "2025-12-23T14:23:49+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f6e6f0a5fa8763f75a504b930163785fb6dd055f" + "reference": "885211d4bed3f857b8c964011923528a55702aa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f6e6f0a5fa8763f75a504b930163785fb6dd055f", - "reference": "f6e6f0a5fa8763f75a504b930163785fb6dd055f", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/885211d4bed3f857b8c964011923528a55702aa5", + "reference": "885211d4bed3f857b8c964011923528a55702aa5", "shasum": "" }, "require": { @@ -6221,7 +6229,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.2" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.3" }, "funding": [ { @@ -6241,20 +6249,20 @@ "type": "tidelift" } ], - "time": "2025-12-08T07:43:37+00:00" + "time": "2025-12-31T08:43:57+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.0", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd" + "reference": "e472d35e230108231ccb7f51eb6b2100cac02ee4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", - "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", + "url": "https://api.github.com/repos/symfony/mailer/zipball/e472d35e230108231ccb7f51eb6b2100cac02ee4", + "reference": "e472d35e230108231ccb7f51eb6b2100cac02ee4", "shasum": "" }, "require": { @@ -6305,7 +6313,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.0" + "source": "https://github.com/symfony/mailer/tree/v7.4.3" }, "funding": [ { @@ -6325,7 +6333,7 @@ "type": "tidelift" } ], - "time": "2025-11-21T15:26:00+00:00" + "time": "2025-12-16T08:02:06+00:00" }, { "name": "symfony/mime", @@ -7247,16 +7255,16 @@ }, { "name": "symfony/process", - "version": "v7.4.0", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8" + "reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", - "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", + "url": "https://api.github.com/repos/symfony/process/zipball/2f8e1a6cdf590ca63715da4d3a7a3327404a523f", + "reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f", "shasum": "" }, "require": { @@ -7288,7 +7296,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.0" + "source": "https://github.com/symfony/process/tree/v7.4.3" }, "funding": [ { @@ -7308,20 +7316,20 @@ "type": "tidelift" } ], - "time": "2025-10-16T11:21:06+00:00" + "time": "2025-12-19T10:00:43+00:00" }, { "name": "symfony/routing", - "version": "v7.4.0", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "4720254cb2644a0b876233d258a32bf017330db7" + "reference": "5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/4720254cb2644a0b876233d258a32bf017330db7", - "reference": "4720254cb2644a0b876233d258a32bf017330db7", + "url": "https://api.github.com/repos/symfony/routing/zipball/5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090", + "reference": "5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090", "shasum": "" }, "require": { @@ -7373,7 +7381,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.0" + "source": "https://github.com/symfony/routing/tree/v7.4.3" }, "funding": [ { @@ -7393,7 +7401,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-12-19T10:00:43+00:00" }, { "name": "symfony/service-contracts", @@ -7575,16 +7583,16 @@ }, { "name": "symfony/translation", - "version": "v7.4.0", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68" + "reference": "7ef27c65d78886f7599fdd5c93d12c9243ecf44d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", - "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", + "url": "https://api.github.com/repos/symfony/translation/zipball/7ef27c65d78886f7599fdd5c93d12c9243ecf44d", + "reference": "7ef27c65d78886f7599fdd5c93d12c9243ecf44d", "shasum": "" }, "require": { @@ -7651,7 +7659,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.0" + "source": "https://github.com/symfony/translation/tree/v7.4.3" }, "funding": [ { @@ -7671,7 +7679,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2025-12-29T09:31:36+00:00" }, { "name": "symfony/translation-contracts", @@ -7835,16 +7843,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.4.0", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece" + "reference": "7e99bebcb3f90d8721890f2963463280848cba92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/41fd6c4ae28c38b294b42af6db61446594a0dece", - "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7e99bebcb3f90d8721890f2963463280848cba92", + "reference": "7e99bebcb3f90d8721890f2963463280848cba92", "shasum": "" }, "require": { @@ -7898,7 +7906,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.3" }, "funding": [ { @@ -7918,7 +7926,146 @@ "type": "tidelift" } ], - "time": "2025-10-27T20:36:44+00:00" + "time": "2025-12-18T07:04:31+00:00" + }, + { + "name": "thecodingmachine/safe", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/2cdd579eeaa2e78e51c7509b50cc9fb89a956236", + "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2025-05-14T06:15:44+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -8439,16 +8586,16 @@ }, { "name": "larastan/larastan", - "version": "v3.8.1", + "version": "v3.9.1", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "ff3725291bc4c7e6032b5a54776e3e5104c86db9" + "reference": "4b92d9627f779fd32bdc16f53f8ce88c50446ff5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/ff3725291bc4c7e6032b5a54776e3e5104c86db9", - "reference": "ff3725291bc4c7e6032b5a54776e3e5104c86db9", + "url": "https://api.github.com/repos/larastan/larastan/zipball/4b92d9627f779fd32bdc16f53f8ce88c50446ff5", + "reference": "4b92d9627f779fd32bdc16f53f8ce88c50446ff5", "shasum": "" }, "require": { @@ -8517,7 +8664,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.8.1" + "source": "https://github.com/larastan/larastan/tree/v3.9.1" }, "funding": [ { @@ -8525,7 +8672,7 @@ "type": "github" } ], - "time": "2025-12-11T16:37:35+00:00" + "time": "2026-01-21T09:15:17+00:00" }, { "name": "mockery/mockery", @@ -8889,11 +9036,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.33", + "version": "2.1.36", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", - "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2132e5e2361d11d40af4c17faa16f043269a4cf3", + "reference": "2132e5e2361d11d40af4c17faa16f043269a4cf3", "shasum": "" }, "require": { @@ -8938,7 +9085,7 @@ "type": "github" } ], - "time": "2025-12-05T10:24:31+00:00" + "time": "2026-01-21T13:58:26+00:00" }, { "name": "phpunit/php-code-coverage", @@ -9277,16 +9424,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.46", + "version": "11.5.48", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "75dfe79a2aa30085b7132bb84377c24062193f33" + "reference": "fe3665c15e37140f55aaf658c81a2eb9030b6d89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/75dfe79a2aa30085b7132bb84377c24062193f33", - "reference": "75dfe79a2aa30085b7132bb84377c24062193f33", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fe3665c15e37140f55aaf658c81a2eb9030b6d89", + "reference": "fe3665c15e37140f55aaf658c81a2eb9030b6d89", "shasum": "" }, "require": { @@ -9300,7 +9447,7 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.11", + "phpunit/php-code-coverage": "^11.0.12", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", @@ -9358,7 +9505,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.46" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.48" }, "funding": [ { @@ -9382,7 +9529,7 @@ "type": "tidelift" } ], - "time": "2025-12-06T08:01:15+00:00" + "time": "2026-01-16T16:26:27+00:00" }, { "name": "sebastian/cli-parser", From 4dc443b7df01eba8e38add743eb0345e6a358f18 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 22 Jan 2026 17:53:58 +0000 Subject: [PATCH 022/204] Updated translations with latest Crowdin changes (#5970) --- lang/bg/auth.php | 6 +-- lang/bg/common.php | 14 ++--- lang/bg/editor.php | 2 +- lang/bg/errors.php | 4 +- lang/cs/errors.php | 2 +- lang/cs/validation.php | 2 +- lang/es/errors.php | 2 +- lang/es/validation.php | 2 +- lang/es_AR/errors.php | 2 +- lang/es_AR/validation.php | 2 +- lang/et/errors.php | 2 +- lang/et/validation.php | 2 +- lang/fr/entities.php | 16 +++--- lang/fr/errors.php | 2 +- lang/fr/notifications.php | 6 +-- lang/fr/preferences.php | 2 +- lang/fr/settings.php | 18 +++---- lang/fr/validation.php | 2 +- lang/hu/activities.php | 12 ++--- lang/hu/common.php | 6 +-- lang/it/errors.php | 2 +- lang/it/notifications.php | 4 +- lang/it/preferences.php | 2 +- lang/it/settings.php | 4 +- lang/it/validation.php | 2 +- lang/ja/errors.php | 2 +- lang/ja/validation.php | 2 +- lang/ko/activities.php | 8 +-- lang/ko/auth.php | 2 +- lang/ko/common.php | 22 ++++---- lang/ko/components.php | 4 +- lang/ko/entities.php | 64 +++++++++++----------- lang/ko/settings.php | 16 +++--- lang/nb/common.php | 4 +- lang/nb/entities.php | 32 +++++------ lang/nb/errors.php | 2 +- lang/nb/notifications.php | 4 +- lang/nb/preferences.php | 2 +- lang/nb/settings.php | 28 +++++----- lang/nb/validation.php | 2 +- lang/nl/errors.php | 2 +- lang/nl/notifications.php | 4 +- lang/nl/preferences.php | 2 +- lang/nl/settings.php | 4 +- lang/nl/validation.php | 2 +- lang/uk/errors.php | 2 +- lang/uk/notifications.php | 4 +- lang/uk/preferences.php | 2 +- lang/uk/settings.php | 12 ++--- lang/uk/validation.php | 2 +- lang/uz/auth.php | 2 +- lang/uz/validation.php | 10 ++-- lang/zh_CN/activities.php | 24 ++++----- lang/zh_CN/entities.php | 100 +++++++++++++++++------------------ lang/zh_CN/errors.php | 4 +- lang/zh_CN/settings.php | 6 +-- lang/zh_TW/errors.php | 2 +- lang/zh_TW/notifications.php | 4 +- lang/zh_TW/preferences.php | 2 +- lang/zh_TW/settings.php | 12 ++--- lang/zh_TW/validation.php | 2 +- 61 files changed, 259 insertions(+), 259 deletions(-) diff --git a/lang/bg/auth.php b/lang/bg/auth.php index 71cd2a71295..4a7e56f22e0 100644 --- a/lang/bg/auth.php +++ b/lang/bg/auth.php @@ -6,7 +6,7 @@ */ return [ - 'failed' => 'Въведените удостоверителни данни не съвпадат с нашите записи.', + 'failed' => 'Въведените данни не съвпадат с информацията в системата.', 'throttle' => 'Твърде много опити за влизане. Опитайте пак след :seconds секунди.', // Login & Register @@ -65,7 +65,7 @@ 'email_confirm_thanks_desc' => 'Почакайте малко, обработвайки потвърждението ви. Ако не сте пренасочени след 3 секунди, то натиснете долу връзката "Продължаване", за да продължите.', 'email_not_confirmed' => 'Имейл адресът не е потвърден', - 'email_not_confirmed_text' => 'Вашият емейл адрес все още не е потвърден.', + 'email_not_confirmed_text' => 'Вашият имейл адрес все още не е потвърден.', 'email_not_confirmed_click_link' => 'Моля да последвате линка, който ви беше изпратен непосредствено след регистрацията.', 'email_not_confirmed_resend' => 'Ако не откривате писмото, може да го изпратите отново като попълните формуляра по-долу.', 'email_not_confirmed_resend_button' => 'Изпрати отново емейла за потвърждение', @@ -91,7 +91,7 @@ 'mfa_option_totp_title' => 'Мобилно приложение', 'mfa_option_totp_desc' => 'За да използваш многофакторно удостоверяване, ще ти трябва мобилно приложение, което поддържа временни еднократни пароли (TOTP), като например Google Authenticator, Authy или Microsoft Authenticator.', 'mfa_option_backup_codes_title' => 'Резервни кодове', - 'mfa_option_backup_codes_desc' => 'Generates a set of one-time-use backup codes which you\'ll enter on login to verify your identity. Make sure to store these in a safe & secure place.', + 'mfa_option_backup_codes_desc' => 'Генерира набор от еднократни резервни кодове, които ще въвеждате при влизане, за да потвърдите самоличността си. Уверете се, че ги съхранявате на безопасно и сигурно място.', 'mfa_gen_confirm_and_enable' => 'Потвърди и включи', 'mfa_gen_backup_codes_title' => 'Настройка на резервни кодове', 'mfa_gen_backup_codes_desc' => 'Запази този лист с кодове на сигурно място. Когато достъпваш системата, ще можеш да използваш един от тези кодове като вторичен механизъм за удостоверяване.', diff --git a/lang/bg/common.php b/lang/bg/common.php index ba74ea5b2cb..d464acb13de 100644 --- a/lang/bg/common.php +++ b/lang/bg/common.php @@ -6,7 +6,7 @@ // Buttons 'cancel' => 'Отказ', - 'close' => 'Close', + 'close' => 'Затвори', 'confirm' => 'Потвърждаване', 'back' => 'Назад', 'save' => 'Запис', @@ -20,7 +20,7 @@ 'description' => 'Описание', 'role' => 'Роля', 'cover_image' => 'Образ на корицата', - 'cover_image_description' => 'This image should be approximately 440x250px although it will be flexibly scaled & cropped to fit the user interface in different scenarios as required, so actual dimensions for display will differ.', + 'cover_image_description' => 'Изображението трябва да е около 440x250 px. Тъй като ще се мащабира и изрязва автоматично спрямо нуждите на интерфейса, крайните размери при показване може да се различават.', // Actions 'actions' => 'Действия', @@ -30,8 +30,8 @@ 'create' => 'Създаване', 'update' => 'Обновяване', 'edit' => 'Редактиране', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'Архивирай', + 'unarchive' => 'Разархивирай', 'sort' => 'Сортиране', 'move' => 'Преместване', 'copy' => 'Копиране', @@ -44,7 +44,7 @@ 'remove' => 'Премахване', 'add' => 'Добавяне', 'configure' => 'Конфигуриране', - 'manage' => 'Manage', + 'manage' => 'Управлявай', 'fullscreen' => 'Цял екран', 'favourite' => 'Любимо', 'unfavourite' => 'Не е любимо', @@ -54,7 +54,7 @@ 'filter_clear' => 'Изчистване на филтрите', 'download' => 'Изтегляне', 'open_in_tab' => 'Отваряне в раздел', - 'open' => 'Open', + 'open' => 'Отвори', // Sort Options 'sort_options' => 'Опции за сортиране', @@ -111,5 +111,5 @@ 'terms_of_service' => 'Условия на услугата', // OpenSearch - 'opensearch_description' => 'Search :appName', + 'opensearch_description' => 'Търси :appName', ]; diff --git a/lang/bg/editor.php b/lang/bg/editor.php index a75128953eb..16c951a71a4 100644 --- a/lang/bg/editor.php +++ b/lang/bg/editor.php @@ -13,7 +13,7 @@ 'cancel' => 'Отказ', 'save' => 'Запис', 'close' => 'Затваряне', - 'apply' => 'Apply', + 'apply' => 'Приложи', 'undo' => 'Отмяна', 'redo' => 'Повтаряне', 'left' => 'Вляво', diff --git a/lang/bg/errors.php b/lang/bg/errors.php index d8dbd4e11fb..9fcda644a1b 100644 --- a/lang/bg/errors.php +++ b/lang/bg/errors.php @@ -10,7 +10,7 @@ // Auth 'error_user_exists_different_creds' => 'Потребител с емайл :email вече съществува но с други данни.', - 'auth_pre_register_theme_prevention' => 'User account could not be registered for the provided details', + 'auth_pre_register_theme_prevention' => 'Потребителски профил не може да бъде създаден с посочената информация', 'email_already_confirmed' => 'Емейлът вече беше потвърден. Моля опитрайте да влезете.', 'email_confirmation_invalid' => 'Този код за достъп не е валиден или вече е бил използван, Моля опитай да се регистрираш отново.', 'email_confirmation_expired' => 'Кодът за потвърждение изтече, нов емейл за потвърждение беше изпратен.', @@ -37,7 +37,7 @@ 'social_driver_not_found' => 'Кодът за връзка със социалната мрежа не съществува', 'social_driver_not_configured' => 'Социалните настройки на твоя :socialAccount не са конфигурирани правилно.', 'invite_token_expired' => 'Твоята покана е изтекла. Вместо това може да пробваш да възстановиш паролата на профила си.', - 'login_user_not_found' => 'A user for this action could not be found.', + 'login_user_not_found' => 'Потребител за това действие не може да бъде намерено.', // System 'path_not_writable' => 'Не може да се качи файл в :filePath. Увери се на сървъра, че в пътя може да се записва.', diff --git a/lang/cs/errors.php b/lang/cs/errors.php index 2077bd4c40e..4f9e350257c 100644 --- a/lang/cs/errors.php +++ b/lang/cs/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => 'Nelze načíst ZIP soubor.', 'import_zip_cant_decode_data' => 'Nelze najít a dekódovat data.json v archivu ZIP.', 'import_zip_no_data' => 'ZIP archiv neobsahuje knihy, kapitoly nebo stránky.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'Obsah souboru data.json v archivu ZIP překračuje maximální povolenou velikost.', 'import_validation_failed' => 'Importování ZIP selhalo s chybami:', 'import_zip_failed_notification' => 'Nepodařilo se naimportovat ZIP soubor.', 'import_perms_books' => 'Chybí vám požadovaná oprávnění k vytvoření knih.', diff --git a/lang/cs/validation.php b/lang/cs/validation.php index 219f95a878f..d14c24d60bb 100644 --- a/lang/cs/validation.php +++ b/lang/cs/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'Nahrávání :attribute se nezdařilo.', 'zip_file' => ':attribute musí odkazovat na soubor v archivu ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => 'Soubor :attribute nesmí překročit :size MB.', 'zip_file_mime' => ':attribute musí odkazovat na soubor typu :validTypes, nalezen :foundType.', 'zip_model_expected' => 'Očekáván datový objekt, ale nalezen „:type“.', 'zip_unique' => ':attribute musí být jedinečný pro typ objektu v archivu ZIP.', diff --git a/lang/es/errors.php b/lang/es/errors.php index 53e035c0de2..2cbe7b9edc1 100644 --- a/lang/es/errors.php +++ b/lang/es/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => 'No se pudo leer el archivo ZIP.', 'import_zip_cant_decode_data' => 'No se pudo encontrar y decodificar el archivo data.json. en el archivo ZIP.', 'import_zip_no_data' => 'Los datos del archivo ZIP no contienen ningún libro, capítulo o contenido de página.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'El contenido del ZIP data.json excede el tamaño máximo de carga configurado.', 'import_validation_failed' => 'Error al validar la importación del ZIP con errores:', 'import_zip_failed_notification' => 'Error al importar archivo ZIP.', 'import_perms_books' => 'Le faltan los permisos necesarios para crear libros.', diff --git a/lang/es/validation.php b/lang/es/validation.php index 1a9aebd4cc9..f0042272a24 100644 --- a/lang/es/validation.php +++ b/lang/es/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'El archivo no ha podido subirse. Es posible que el servidor no acepte archivos de este tamaño.', 'zip_file' => 'El :attribute necesita hacer referencia a un archivo dentro del ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => 'El archivo :attribute no debe exceder :size MB.', 'zip_file_mime' => 'El :attribute necesita hacer referencia a un archivo de tipo :validTypes, encontrado :foundType.', 'zip_model_expected' => 'Se esperaba un objeto de datos, pero se encontró ":type".', 'zip_unique' => 'El :attribute debe ser único para el tipo de objeto dentro del ZIP.', diff --git a/lang/es_AR/errors.php b/lang/es_AR/errors.php index 7bc3a189a0b..08745cf30da 100644 --- a/lang/es_AR/errors.php +++ b/lang/es_AR/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => 'No se pudo leer el archivo ZIP.', 'import_zip_cant_decode_data' => 'No se pudo encontrar ni decodificar el contenido del archivo ZIP data.json.', 'import_zip_no_data' => 'Los datos del archivo ZIP no tienen un libro, un capítulo o contenido de página en su contenido.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'El contenido del ZIP data.json excede el tamaño máximo de carga configurado.', 'import_validation_failed' => 'Error al validar la importación del ZIP con los errores:', 'import_zip_failed_notification' => 'Error al importar archivo ZIP.', 'import_perms_books' => 'Le faltan los permisos necesarios para crear libros.', diff --git a/lang/es_AR/validation.php b/lang/es_AR/validation.php index 1073933fe16..9516401b91d 100644 --- a/lang/es_AR/validation.php +++ b/lang/es_AR/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'El archivo no se pudo subir. Puede ser que el servidor no acepte archivos de este tamaño.', 'zip_file' => 'El :attribute necesita hacer referencia a un archivo dentro del ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => 'El archivo :attribute no debe exceder :size MB.', 'zip_file_mime' => 'El :attribute necesita hacer referencia a un archivo de tipo :validTypes, encontrado :foundType.', 'zip_model_expected' => 'Se esperaba un objeto de datos, pero se encontró ":type".', 'zip_unique' => 'El :attribute debe ser único para el tipo de objeto dentro del ZIP.', diff --git a/lang/et/errors.php b/lang/et/errors.php index e1c95199386..8ee50f4f78b 100644 --- a/lang/et/errors.php +++ b/lang/et/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => 'ZIP-faili lugemine ebaõnnestus.', 'import_zip_cant_decode_data' => 'ZIP-failist ei leitud data.json sisu.', 'import_zip_no_data' => 'ZIP-failist ei leitud raamatute, peatükkide või lehtede sisu.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'ZIP-faili data.json sisu ületab rakenduses seadistatud maksimaalse failisuuruse.', 'import_validation_failed' => 'Imporditud ZIP-faili valideerimine ebaõnnestus vigadega:', 'import_zip_failed_notification' => 'ZIP-faili importimine ebaõnnestus.', 'import_perms_books' => 'Sul puuduvad õigused raamatute lisamiseks.', diff --git a/lang/et/validation.php b/lang/et/validation.php index 947007d2670..e9a7234c9da 100644 --- a/lang/et/validation.php +++ b/lang/et/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'Faili üleslaadimine ebaõnnestus. Server ei pruugi sellise suurusega faile vastu võtta.', 'zip_file' => ':attribute peab viitama failile ZIP-arhiivi sees.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => 'Fail :attribute ei tohi olla suurem kui :size MB.', 'zip_file_mime' => ':attribute peab viitama :validTypes tüüpi failile, leiti :foundType.', 'zip_model_expected' => 'Oodatud andmete asemel leiti ":type".', 'zip_unique' => ':attribute peab olema ZIP-arhiivi piires objekti tüübile unikaalne.', diff --git a/lang/fr/entities.php b/lang/fr/entities.php index ecf26442bea..d650fc83058 100644 --- a/lang/fr/entities.php +++ b/lang/fr/entities.php @@ -54,7 +54,7 @@ 'import_continue_desc' => 'Examinez le contenu à importer à partir du fichier ZIP téléchargé. Lorsque vous êtes prêt, lancez l\'importation pour ajouter son contenu à ce système. Le fichier d\'importation ZIP téléchargé sera automatiquement supprimé si l\'importation est réussie.', 'import_details' => 'Détails de l\'importation', 'import_run' => 'Exécuter Importation', - 'import_size' => ':size taille du ZIP d\'import', + 'import_size' => ':size Taille du fichier ZIP à importer', 'import_uploaded_at' => ':relativeTime téléchargé', 'import_uploaded_by' => 'Téléchargé par', 'import_location' => 'Emplacement de l\'importation', @@ -330,13 +330,13 @@ // Editor Sidebar 'toggle_sidebar' => 'Afficher/masquer la barre latérale', - 'page_tags' => 'Mots-clés de la page', - 'chapter_tags' => 'Mots-clés du chapitre', - 'book_tags' => 'Mots-clés du livre', - 'shelf_tags' => 'Mots-clés de l\'étagère', - 'tag' => 'Mot-clé', - 'tags' => 'Mots-clés', - 'tags_index_desc' => 'Les tags peuvent être appliqués au contenu du système pour appliquer une forme flexible de catégorisation. Les tags peuvent avoir à la fois une clé et une valeur, la valeur étant facultative. Une fois appliqué, le contenu peut ensuite être interrogé à l’aide du nom et de la valeur du tag.', + 'page_tags' => 'Étiquettes de la page', + 'chapter_tags' => 'Étiquettes du chapitre', + 'book_tags' => 'Étiquettes du livre', + 'shelf_tags' => 'Étiquettes de l\'étagère', + 'tag' => 'Étiquette', + 'tags' => 'Étiquettes', + 'tags_index_desc' => 'Les étiquettes peuvent être mises sur le contenu pour appliquer une forme flexible de catégorisation. Les étiquettes peuvent avoir à la fois une clé et une valeur, la valeur étant facultative. Une fois appliqué, le contenu peut ensuite être interrogé à l’aide du nom et de la valeur de l’étiquette.', 'tag_name' => 'Nom de l’étiquette', 'tag_value' => 'Valeur du mot-clé (optionnel)', 'tags_explain' => "Ajouter des mots-clés pour catégoriser votre contenu.", diff --git a/lang/fr/errors.php b/lang/fr/errors.php index a4fb9b565bc..0bc8e491765 100644 --- a/lang/fr/errors.php +++ b/lang/fr/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => 'Impossible de lire le fichier ZIP.', 'import_zip_cant_decode_data' => 'Impossible de trouver et de décoder le contenu ZIP data.json.', 'import_zip_no_data' => 'Les données du fichier ZIP n\'ont pas de livre, de chapitre ou de page attendus.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'Le contenu du fichier ZIP pour data.json dépasse la taille maximale de téléversement autorisée.', 'import_validation_failed' => 'L\'importation du ZIP n\'a pas été validée avec les erreurs :', 'import_zip_failed_notification' => 'Impossible d\'importer le fichier ZIP.', 'import_perms_books' => 'Vous n\'avez pas les permissions requises pour créer des livres.', diff --git a/lang/fr/notifications.php b/lang/fr/notifications.php index b82f82bd0b8..dc4127b032f 100644 --- a/lang/fr/notifications.php +++ b/lang/fr/notifications.php @@ -4,15 +4,15 @@ */ return [ - 'new_comment_subject' => 'Nouveau commentaire sur la page: :pageName', + 'new_comment_subject' => 'Nouveau commentaire sur la page : :pageName', 'new_comment_intro' => 'Un utilisateur a commenté une page dans :appName:', 'new_page_subject' => 'Nouvelle page: :pageName', 'new_page_intro' => 'Une nouvelle page a été créée dans :appName:', 'updated_page_subject' => 'Page mise à jour: :pageName', 'updated_page_intro' => 'Une page a été mise à jour dans :appName:', 'updated_page_debounce' => 'Pour éviter de nombreuses notifications, pendant un certain temps, vous ne recevrez pas de notifications pour d\'autres modifications de cette page par le même éditeur.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'Vous avez été mentionné dans un commentaire sur la page : :pageName', + 'comment_mention_intro' => 'Vous avez été mentionné dans un commentaire sur :appName:', 'detail_page_name' => 'Nom de la page :', 'detail_page_path' => 'Chemin de la page :', diff --git a/lang/fr/preferences.php b/lang/fr/preferences.php index c595222f1b8..dbae975f31d 100644 --- a/lang/fr/preferences.php +++ b/lang/fr/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'Contrôlez les notifications par e-mail que vous recevez lorsque certaines activités sont effectuées dans le système.', 'notifications_opt_own_page_changes' => 'Notifier lors des modifications des pages que je possède', 'notifications_opt_own_page_comments' => 'Notifier lorsque les pages que je possède sont commentées', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'Notifier lorsque je suis mentionné dans un commentaire', 'notifications_opt_comment_replies' => 'Notifier les réponses à mes commentaires', 'notifications_save' => 'Enregistrer les préférences', 'notifications_update_success' => 'Les préférences de notification ont été mises à jour !', diff --git a/lang/fr/settings.php b/lang/fr/settings.php index 7ce2312f3bf..317e777b972 100644 --- a/lang/fr/settings.php +++ b/lang/fr/settings.php @@ -17,14 +17,14 @@ 'app_features_security' => 'Fonctionnalités et sécurité', 'app_name' => 'Nom de l\'application', 'app_name_desc' => 'Ce nom est affiché dans l\'en-tête et les e-mails.', - 'app_name_header' => 'Afficher le nom dans l\'en-tête ?', + 'app_name_header' => 'Afficher le nom dans l\'en-tête', 'app_public_access' => 'Accès public', 'app_public_access_desc' => 'L\'activation de cette option permettra aux visiteurs, qui ne sont pas connectés, d\'accéder au contenu de votre instance BookStack.', 'app_public_access_desc_guest' => 'L\'accès pour les visiteurs publics peut être contrôlé par l\'utilisateur "Guest".', 'app_public_access_toggle' => 'Autoriser l\'accès public', 'app_public_viewing' => 'Accepter l\'affichage public des pages ?', 'app_secure_images' => 'Ajout d\'image sécurisé', - 'app_secure_images_toggle' => 'Activer l\'ajout d\'image sécurisé', + 'app_secure_images_toggle' => 'Activer l\'ajout d\'image sécurisée', 'app_secure_images_desc' => 'Pour des questions de performances, toutes les images sont publiques. Cette option ajoute une chaîne aléatoire difficile à deviner dans les URLs des images.', 'app_default_editor' => 'Éditeur de page par défaut', 'app_default_editor_desc' => 'Sélectionnez l\'éditeur qui sera utilisé par défaut lors de l\'édition de nouvelles pages. Cela peut être remplacé au niveau de la page où les permissions sont autorisées.', @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Aucune restriction en place', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', + 'sorting' => 'Listes et tri', + 'sorting_book_default' => 'Tri des livres par défaut', 'sorting_book_default_desc' => 'Sélectionnez le tri par défaut à mettre en place sur les nouveaux livres. Cela n’affectera pas les livres existants, et peut être redéfini dans les livres.', 'sorting_rules' => 'Règles de tri', 'sorting_rules_desc' => 'Ce sont les opérations de tri qui peuvent être appliquées au contenu du système.', @@ -103,8 +103,8 @@ 'sort_rule_op_updated_date' => 'Date de mise à jour', 'sort_rule_op_chapters_first' => 'Chapitres en premier', 'sort_rule_op_chapters_last' => 'Chapitres en dernier', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits' => 'Limite d\'affichage par page', + 'sorting_page_limits_desc' => 'Définissez le nombre d’éléments à afficher par page dans les différentes listes du système. En général, un nombre plus faible offre de meilleures performances, tandis qu’un nombre plus élevé réduit le besoin de naviguer entre plusieurs pages. Il est recommandé d’utiliser un multiple pair de 3 (18, 24, 30, etc.).', // Maintenance settings 'maint' => 'Maintenance', @@ -197,13 +197,13 @@ 'role_import_content' => 'Importer le contenu', 'role_editor_change' => 'Changer l\'éditeur de page', 'role_notifications' => 'Recevoir et gérer les notifications', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => 'Ces autorisations permettront également l\'accès à la consultation et la recherche des utilisateurs et des rôles dans le système.', 'role_asset' => 'Permissions des ressources', 'roles_system_warning' => 'Sachez que l\'accès à l\'une des trois permissions ci-dessus peut permettre à un utilisateur de modifier ses propres privilèges ou les privilèges des autres utilisateurs du système. N\'attribuez uniquement des rôles avec ces permissions qu\'à des utilisateurs de confiance.', 'role_asset_desc' => 'Ces permissions contrôlent l\'accès par défaut des ressources dans le système. Les permissions dans les livres, les chapitres et les pages ignoreront ces permissions', 'role_asset_admins' => 'Les administrateurs ont automatiquement accès à tous les contenus mais les options suivantes peuvent afficher ou masquer certaines options de l\'interface.', 'role_asset_image_view_note' => 'Cela concerne la visibilité dans le gestionnaire d\'images. L\'accès réel des fichiers d\'image téléchargés dépendra de l\'option de stockage d\'images du système.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Ces autorisations permettront également l\'accès à la consultation et la recherche des utilisateurs dans le système.', 'role_all' => 'Tous', 'role_own' => 'Propres', 'role_controlled_by_asset' => 'Contrôlé par les ressources les ayant envoyés', @@ -270,7 +270,7 @@ 'user_api_token_name_desc' => 'Donnez à votre jeton un nom lisible pour l\'identifier plus tard.', 'user_api_token_expiry' => 'Date d\'expiration', 'user_api_token_expiry_desc' => 'Définissez une date à laquelle ce jeton expire. Après cette date, les demandes effectuées à l\'aide de ce jeton ne fonctionneront plus. Le fait de laisser ce champ vide entraînera une expiration dans 100 ans.', - 'user_api_token_create_secret_message' => 'Immédiatement après la création de ce jeton, un "ID de jeton" "et" Secret de jeton "sera généré et affiché. Le secret ne sera affiché qu\'une seule fois, alors assurez-vous de copier la valeur dans un endroit sûr et sécurisé avant de continuer.', + 'user_api_token_create_secret_message' => 'Immédiatement après la création de ce jeton, un "ID de jeton" et "Secret de jeton" sera généré et affiché. Le secret ne sera affiché qu\'une seule fois, alors assurez-vous de copier la valeur dans un endroit sûr et sécurisé avant de continuer.', 'user_api_token' => 'Jeton API', 'user_api_token_id' => 'Token ID', 'user_api_token_id_desc' => 'Il s\'agit d\'un identifiant généré par le système non modifiable pour ce jeton qui devra être fourni dans les demandes d\'API.', diff --git a/lang/fr/validation.php b/lang/fr/validation.php index 74ecca12e38..f2a9f5dc77c 100644 --- a/lang/fr/validation.php +++ b/lang/fr/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'Le fichier n\'a pas pu être envoyé. Le serveur peut ne pas accepter des fichiers de cette taille.', 'zip_file' => 'L\'attribut :attribute doit référencer un fichier dans le ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => 'Le fichier :attribute ne doit pas dépasser :size Mo.', 'zip_file_mime' => ':attribute doit référencer un fichier de type :validTypes, trouvé :foundType.', 'zip_model_expected' => 'Objet de données attendu, mais ":type" trouvé.', 'zip_unique' => 'L\'attribut :attribute doit être unique pour le type d\'objet dans le ZIP.', diff --git a/lang/hu/activities.php b/lang/hu/activities.php index 43683930bea..68184e597b4 100644 --- a/lang/hu/activities.php +++ b/lang/hu/activities.php @@ -85,12 +85,12 @@ 'webhook_delete_notification' => 'Webhook sikeresen törölve', // Imports - 'import_create' => 'created import', - 'import_create_notification' => 'Import successfully uploaded', - 'import_run' => 'updated import', - 'import_run_notification' => 'Content successfully imported', - 'import_delete' => 'deleted import', - 'import_delete_notification' => 'Import successfully deleted', + 'import_create' => 'import elkészült', + 'import_create_notification' => 'Az import sikeresen feltöltötve', + 'import_run' => 'import frissítve', + 'import_run_notification' => 'A tartalmat sikeresen importáltam.', + 'import_delete' => 'import törölve', + 'import_delete_notification' => 'Az import sikeresen törölve', // Users 'user_create' => 'létrehozta a felhasználót', diff --git a/lang/hu/common.php b/lang/hu/common.php index e65e6890921..d25a765283d 100644 --- a/lang/hu/common.php +++ b/lang/hu/common.php @@ -30,8 +30,8 @@ 'create' => 'Létrehozás', 'update' => 'Frissítés', 'edit' => 'Szerkesztés', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'Archiválás', + 'unarchive' => 'Archiválás visszavonása', 'sort' => 'Rendezés', 'move' => 'Áthelyezés', 'copy' => 'Másolás', @@ -111,5 +111,5 @@ 'terms_of_service' => 'Felhasználási feltételek', // OpenSearch - 'opensearch_description' => 'Search :appName', + 'opensearch_description' => 'Keresés :appName', ]; diff --git a/lang/it/errors.php b/lang/it/errors.php index 62b698accfb..62f294842e1 100644 --- a/lang/it/errors.php +++ b/lang/it/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => 'Impossibile leggere il file ZIP.', 'import_zip_cant_decode_data' => 'Impossibile trovare e decodificare il contenuto ZIP data.json.', 'import_zip_no_data' => 'I dati del file ZIP non hanno il contenuto previsto di libri, capitoli o pagine.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'Il contenuto ZIP data.json supera la dimensione massima di upload configurata nell\'applicazione.', 'import_validation_failed' => 'L\'importazione ZIP non è stata convalidata con errori:', 'import_zip_failed_notification' => 'Impossibile importare il file ZIP.', 'import_perms_books' => 'Non hai i permessi necessari per creare libri.', diff --git a/lang/it/notifications.php b/lang/it/notifications.php index a4e57abdf65..a8b23f1a719 100644 --- a/lang/it/notifications.php +++ b/lang/it/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => 'Pagina aggiornata: :pageName', 'updated_page_intro' => 'Una pagina è stata aggiornata in :appName:', 'updated_page_debounce' => 'Per evitare una massa di notifiche, per un po\' non ti verranno inviate notifiche per ulteriori modifiche a questa pagina dallo stesso editor.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'Sei stato menzionato in un commento nella pagina: :pageName', + 'comment_mention_intro' => 'Sei stato menzionato in un commento su :appName:', 'detail_page_name' => 'Nome della pagina:', 'detail_page_path' => 'Percorso della pagina:', diff --git a/lang/it/preferences.php b/lang/it/preferences.php index db6b907541d..ee52e1d200e 100644 --- a/lang/it/preferences.php +++ b/lang/it/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'Controlla le notifiche email che ricevi quando viene eseguita una determinata attività all\'interno del sistema.', 'notifications_opt_own_page_changes' => 'Notifica in caso di modifiche alle pagine che possiedo', 'notifications_opt_own_page_comments' => 'Notifica i commenti sulle pagine che possiedo', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'Avvisami quando vengo menzionato in un commento', 'notifications_opt_comment_replies' => 'Notificare le risposte ai miei commenti', 'notifications_save' => 'Salva preferenze', 'notifications_update_success' => 'Le preferenze di notifica sono state aggiornate!', diff --git a/lang/it/settings.php b/lang/it/settings.php index 9f2d882af40..88c6a4c11bf 100644 --- a/lang/it/settings.php +++ b/lang/it/settings.php @@ -197,13 +197,13 @@ 'role_import_content' => 'Importa contenuto', 'role_editor_change' => 'Cambiare editor di pagina', 'role_notifications' => 'Ricevere e gestire le notifiche', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => 'Queste autorizzazioni forniranno tecnicamente anche la visibilità e la ricerca di utenti e ruoli nel sistema.', 'role_asset' => 'Permessi entità', 'roles_system_warning' => 'Siate consapevoli che l\'accesso a uno dei tre permessi qui sopra può consentire a un utente di modificare i propri privilegi o i privilegi di altri nel sistema. Assegna ruoli con questi permessi solo ad utenti fidati.', 'role_asset_desc' => 'Questi permessi controllano l\'accesso predefinito alle entità. I permessi in libri, capitoli e pagine sovrascriveranno questi.', 'role_asset_admins' => 'Gli amministratori hanno automaticamente accesso a tutti i contenuti ma queste opzioni possono mostrare o nascondere le opzioni della UI.', 'role_asset_image_view_note' => 'Questo si riferisce alla visibilità all\'interno del gestore delle immagini. L\'accesso effettivo ai file di immagine caricati dipenderà dall\'opzione di archiviazione delle immagini di sistema.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Queste autorizzazioni forniranno tecnicamente anche la visibilità e la ricerca di utenti nel sistema.', 'role_all' => 'Tutti', 'role_own' => 'Propri', 'role_controlled_by_asset' => 'Controllato dall\'entità in cui sono caricati', diff --git a/lang/it/validation.php b/lang/it/validation.php index 54ef4dba68f..972f86e5227 100644 --- a/lang/it/validation.php +++ b/lang/it/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'Il file non può essere caricato. Il server potrebbe non accettare file di questa dimensione.', 'zip_file' => 'L\'attributo :attribute deve fare riferimento a un file all\'interno dello ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => 'Il file :attribute non deve superare :size MB.', 'zip_file_mime' => 'Il campo :attribute deve fare riferimento a un file di tipo :validTypes, trovato :foundType.', 'zip_model_expected' => 'Oggetto dati atteso ma ":type" trovato.', 'zip_unique' => 'L\'attributo :attribute deve essere univoco per il tipo di oggetto all\'interno dello ZIP.', diff --git a/lang/ja/errors.php b/lang/ja/errors.php index 443a810f939..cad2e5486f9 100644 --- a/lang/ja/errors.php +++ b/lang/ja/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => 'ZIPファイルを読み込めません。', 'import_zip_cant_decode_data' => 'ZIPファイル内に data.json が見つからないかデコードできませんでした。', 'import_zip_no_data' => 'ZIPファイルのデータにブック、チャプター、またはページコンテンツがありません。', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'ZIPに含まれる data.json が、アプリケーションで設定された最大アップロードサイズを超えています。', 'import_validation_failed' => 'エラーによりインポートZIPの検証に失敗しました:', 'import_zip_failed_notification' => 'ZIP ファイルのインポートに失敗しました。', 'import_perms_books' => 'ブックを作成するために必要な権限がありません。', diff --git a/lang/ja/validation.php b/lang/ja/validation.php index 0efbc7d682d..d7617b5d3d9 100644 --- a/lang/ja/validation.php +++ b/lang/ja/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'ファイルをアップロードできませんでした。サーバーがこのサイズのファイルを受け付けていない可能性があります。', 'zip_file' => ':attribute はZIP 内のファイルを参照する必要があります。', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => ':attribute は :size MB を超えてはいけません。', 'zip_file_mime' => ':attribute は種別 :validType のファイルを参照する必要がありますが、種別 :foundType となっています。', 'zip_model_expected' => 'データオブジェクトが期待されますが、":type" が見つかりました。', 'zip_unique' => 'ZIP内のオブジェクトタイプに :attribute が一意である必要があります。', diff --git a/lang/ko/activities.php b/lang/ko/activities.php index c1dbb93fd7b..061b7fb1e5c 100644 --- a/lang/ko/activities.php +++ b/lang/ko/activities.php @@ -33,7 +33,7 @@ 'book_create_from_chapter' => '챕터를 책으로 변환', 'book_create_from_chapter_notification' => '챕터가 책으로 성공적으로 변환되었습니다.', 'book_update' => '업데이트된 책', - 'book_update_notification' => '책이 성공적으로 업데이트되었습니다.', + 'book_update_notification' => '책 수정함', 'book_delete' => '삭제된 책', 'book_delete_notification' => '책이 성공적으로 삭제되었습니다.', 'book_sort' => '책 정렬', @@ -50,9 +50,9 @@ 'bookshelf_delete_notification' => '책장이 성공적으로 삭제되었습니다.', // Revisions - 'revision_restore' => '버전 복구', - 'revision_delete' => '버전 삭제', - 'revision_delete_notification' => '버전 삭제 성공', + 'revision_restore' => '복원한 수정본', + 'revision_delete' => '삭제한 수정본', + 'revision_delete_notification' => '수정본을 잘 삭제함', // Favourites 'favourite_add_notification' => '":name" 을 북마크에 추가하였습니다.', diff --git a/lang/ko/auth.php b/lang/ko/auth.php index 6b60225cbd4..af9129f14e1 100644 --- a/lang/ko/auth.php +++ b/lang/ko/auth.php @@ -17,7 +17,7 @@ 'logout' => '로그아웃', 'name' => '이름', - 'username' => '사용자 이름', + 'username' => '이용자명', 'email' => '전자우편 주소', 'password' => '비밀번호', 'password_confirm' => '비밀번호 확인', diff --git a/lang/ko/common.php b/lang/ko/common.php index ff35f097179..9f0e9990f0c 100644 --- a/lang/ko/common.php +++ b/lang/ko/common.php @@ -30,8 +30,8 @@ 'create' => '만들기', 'update' => '바꾸기', 'edit' => '수정', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => '보관', + 'unarchive' => '보관 해제', 'sort' => '정렬', 'move' => '이동', 'copy' => '복사', @@ -43,31 +43,31 @@ 'reset' => '리셋', 'remove' => '제거', 'add' => '추가', - 'configure' => '설정', + 'configure' => '구성', 'manage' => '관리', 'fullscreen' => '전체화면', 'favourite' => '즐겨찾기', 'unfavourite' => '즐겨찾기 해제', 'next' => '다음', 'previous' => '이전', - 'filter_active' => '적용 중:', - 'filter_clear' => '모든 필터 해제', + 'filter_active' => '적용 필터:', + 'filter_clear' => '필터 해제', 'download' => '내려받기', 'open_in_tab' => '탭에서 열기', - 'open' => '열기 ', + 'open' => '열기', // Sort Options 'sort_options' => '정렬 기준', 'sort_direction_toggle' => '순서 반전', 'sort_ascending' => '오름차순', 'sort_descending' => '내림차순', - 'sort_name' => '제목', + 'sort_name' => '이름', 'sort_default' => '기본값', 'sort_created_at' => '만든 날짜', - 'sort_updated_at' => '수정한 날짜', + 'sort_updated_at' => '갱신한 날짜', // Misc - 'deleted_user' => '삭제한 사용자', + 'deleted_user' => '삭제한 이용자', 'no_activity' => '활동 없음', 'no_items' => '항목 없음', 'back_to_top' => '맨 위로', @@ -75,8 +75,8 @@ 'toggle_details' => '내용 보기', 'toggle_thumbnails' => '썸네일 보기', 'details' => '정보', - 'grid_view' => '격자 형식으로 보기', - 'list_view' => '리스트 형식으로 보기', + 'grid_view' => '격자로 보기', + 'list_view' => '목록으로 보기', 'default' => '기본 설정', 'breadcrumb' => '탐색 경로', 'status' => '상태', diff --git a/lang/ko/components.php b/lang/ko/components.php index 0c0bc2e65ed..7995ceebd9a 100644 --- a/lang/ko/components.php +++ b/lang/ko/components.php @@ -8,7 +8,7 @@ 'image_select' => '이미지 선택', 'image_list' => '이미지 목록', 'image_details' => '이미지 상세정보', - 'image_upload' => '이미지 업로드', + 'image_upload' => '이미지 올려두기', 'image_intro' => '여기에서 이전에 시스템에 업로드한 이미지를 선택하고 관리할 수 있습니다.', 'image_intro_upload' => '이미지 파일을 이 창으로 끌어다 놓거나 위의 \'이미지 업로드\' 버튼을 사용하여 새 이미지를 업로드합니다.', 'image_all' => '모든 이미지', @@ -17,7 +17,7 @@ 'image_page_title' => '이 문서에서 쓰고 있는 이미지', 'image_search_hint' => '이미지 이름 검색', 'image_uploaded' => '올림 :uploadedDate', - 'image_uploaded_by' => '업로드 :userName', + 'image_uploaded_by' => ':userName 이용자가 올려둠', 'image_uploaded_to' => ':pageLink 로 업로드됨', 'image_updated' => '갱신일 :updateDate', 'image_load_more' => '더 보기', diff --git a/lang/ko/entities.php b/lang/ko/entities.php index b5624f2213b..44809d99ff4 100644 --- a/lang/ko/entities.php +++ b/lang/ko/entities.php @@ -16,8 +16,8 @@ 'recently_viewed' => '최근에 본 목록', 'recent_activity' => '최근 활동 기록', 'create_now' => '바로 만들기', - 'revisions' => '버전', - 'meta_revision' => '버전 #:revisionCount', + 'revisions' => '수정본', + 'meta_revision' => '수정본 #:revisionCount', 'meta_created' => '생성 :timeLength', 'meta_created_name' => '생성 :timeLength, :user', 'meta_updated' => '수정 :timeLength', @@ -115,7 +115,7 @@ 'shelves_create' => '책꽂이 만들기', 'shelves_popular' => '많이 읽은 책꽂이', 'shelves_new' => '새로운 책꽂이', - 'shelves_new_action' => '새로운 책꽂이', + 'shelves_new_action' => '새 책꽂이', 'shelves_popular_empty' => '많이 읽은 책꽂이 목록', 'shelves_new_empty' => '새로운 책꽂이 목록', 'shelves_save' => '저장', @@ -148,7 +148,7 @@ 'books_popular' => '많이 읽은 책', 'books_recent' => '최근에 읽은 책', 'books_new' => '새로운 책', - 'books_new_action' => '새로운 책', + 'books_new_action' => '새 책', 'books_popular_empty' => '많이 읽은 책 목록', 'books_new_empty' => '새로운 책 목록', 'books_create' => '책 만들기', @@ -200,7 +200,7 @@ 'chapters' => '챕터', 'x_chapters' => '챕터 :count개|총 :count개', 'chapters_popular' => '많이 읽은 챕터', - 'chapters_new' => '새로운 챕터', + 'chapters_new' => '새 장', 'chapters_create' => '챕터 만들기', 'chapters_delete' => '챕터 삭제하기', 'chapters_delete_named' => ':chapterName(을)를 지웁니다.', @@ -221,11 +221,11 @@ 'chapter_sort_book' => '책 정렬하기', // Pages - 'page' => '문서', - 'pages' => '문서', + 'page' => '페이지', + 'pages' => '페이지', 'x_pages' => '문서 :count개|총 :count개', 'pages_popular' => '많이 읽은 문서', - 'pages_new' => '새로운 문서', + 'pages_new' => '새 페이지', 'pages_attachments' => '첨부', 'pages_navigation' => '목차', 'pages_delete' => '문서 삭제하기', @@ -272,7 +272,7 @@ 'pages_md_insert_drawing' => '드로잉 추가', 'pages_md_show_preview' => '미리보기 표시', 'pages_md_sync_scroll' => '미리보기 스크롤 동기화', - 'pages_md_plain_editor' => 'Plaintext editor', + 'pages_md_plain_editor' => '플레인텍스트 편집기', 'pages_drawing_unsaved' => '저장되지 않은 드로잉 발견', 'pages_drawing_unsaved_confirm' => '이전에 실패한 드로잉 저장 시도에서 저장되지 않은 드로잉 데이터가 발견되었습니다. 이 저장되지 않은 드로잉을 복원하고 계속 편집하시겠습니까?', 'pages_not_in_chapter' => '챕터에 있는 문서가 아닙니다.', @@ -290,10 +290,10 @@ 'pages_revision_restored_from' => '#:id; :summary에서 복구함', 'pages_revisions_created_by' => '만든 사용자', 'pages_revisions_date' => '수정한 날짜', - 'pages_revisions_number' => 'No.', + 'pages_revisions_number' => '#', 'pages_revisions_sort_number' => '수정 번호', - 'pages_revisions_numbered' => '수정본 :id', - 'pages_revisions_numbered_changes' => '수정본 :id에서 바꾼 부분', + 'pages_revisions_numbered' => '수정본 #:id', + 'pages_revisions_numbered_changes' => '수정본 #:id에서 바꾼 부분', 'pages_revisions_editor' => '편집기 유형', 'pages_revisions_changelog' => '설명', 'pages_revisions_changes' => '바꾼 부분', @@ -310,7 +310,7 @@ 'pages_pointer_toggle_link' => '퍼머링크 모드, 포함 태그를 표시하려면 누릅니다.', 'pages_pointer_toggle_include' => '태그 포함 모드, 퍼머링크를 표시하려면 누릅니다.', 'pages_permissions_active' => '문서 권한 허용함', - 'pages_initial_revision' => '처음 판본', + 'pages_initial_revision' => '최초 게시', 'pages_references_update_revision' => '시스템에서 내부 링크 자동 업데이트', 'pages_initial_name' => '제목 없음', 'pages_editing_draft_notification' => ':timeDiff에 초안 문서입니다.', @@ -330,27 +330,27 @@ // Editor Sidebar 'toggle_sidebar' => '사이드바 토글', - 'page_tags' => '문서 태그', - 'chapter_tags' => '챕터 꼬리표', - 'book_tags' => '책 꼬리표', - 'shelf_tags' => '책꽂이 꼬리표', - 'tag' => '꼬리표', - 'tags' => '꼬리표', + 'page_tags' => '페이지 태그', + 'chapter_tags' => '장 태그', + 'book_tags' => '책 태그', + 'shelf_tags' => '책꽂이 태그', + 'tag' => '태그', + 'tags' => '태그', 'tags_index_desc' => '태그를 시스템 내의 콘텐츠에 적용하여 유연한 형태의 분류를 적용할 수 있습니다. 태그는 키와 값을 모두 가질 수 있으며 값은 선택 사항입니다. 태그가 적용되면 태그 이름과 값을 사용하여 콘텐츠를 쿼리할 수 있습니다.', - 'tag_name' => '꼬리표 이름', + 'tag_name' => '태그 이름', 'tag_value' => '리스트 값 (선택 사항)', 'tags_explain' => "문서를 더 잘 분류하려면 태그를 추가하세요.\n태그에 값을 할당하여 더욱 체계적으로 구성할 수 있습니다.", - 'tags_add' => '꼬리표 추가', - 'tags_remove' => '꼬리표 삭제', - 'tags_usages' => '모든 꼬리표', - 'tags_assigned_pages' => '문서에 꼬리표 지정함', - 'tags_assigned_chapters' => '챕터에 꼬리표 지정함', - 'tags_assigned_books' => '책에 태그 지정함', - 'tags_assigned_shelves' => '책꽂이에 꼬리표 지정함', + 'tags_add' => '다른 태그 추가하기', + 'tags_remove' => '이 태그 제거하기', + 'tags_usages' => '전체 태그 이용량', + 'tags_assigned_pages' => '| 페이지 태그 할당 |', + 'tags_assigned_chapters' => '| 장 태그 할당 |', + 'tags_assigned_books' => '| 책 태그 할당 |', + 'tags_assigned_shelves' => '| 책꽂이 태그 할당 |', 'tags_x_unique_values' => ':count 중복 없는 값', 'tags_all_values' => '모든 값', - 'tags_view_tags' => '꼬리표 보기', - 'tags_view_existing_tags' => '사용 중인 꼬리표 보기', + 'tags_view_tags' => '태그 보기', + 'tags_view_existing_tags' => '기존 태그 보기', 'tags_list_empty_hint' => '태그는 에디터 사이드바나 책, 챕터 또는 책꽂이 정보 편집에서 지정할 수 있습니다.', 'attachments' => '첨부 파일', 'attachments_explain' => '파일이나 링크를 첨부하세요. 정보 탭에 나타납니다.', @@ -403,7 +403,7 @@ 'comment_archived_count' => ':count Archived', 'comment_archived_threads' => 'Archived Threads', 'comment_save' => '등록', - 'comment_new' => '새로운 댓글', + 'comment_new' => '새 의견', 'comment_created' => '댓글 등록함 :createDiff', 'comment_updated' => ':username(이)가 댓글 수정함 :updateDiff', 'comment_updated_indicator' => '업데이트됨', @@ -416,14 +416,14 @@ 'comment_jump_to_thread' => 'Jump to thread', 'comment_delete_confirm' => '이 댓글을 지울 건가요?', 'comment_in_reply_to' => ':commentId(을)를 향한 답글', - 'comment_reference' => 'Reference', + 'comment_reference' => '참조', 'comment_reference_outdated' => '(Outdated)', 'comment_editor_explain' => '이 페이지에 남겨진 댓글은 다음과 같습니다. 저장된 페이지를 볼 때 댓글을 추가하고 관리할 수 있습니다.', // Revision 'revision_delete_confirm' => '이 수정본을 지울 건가요?', 'revision_restore_confirm' => '이 버전을 되돌릴 건가요? 현재 페이지는 대체됩니다.', - 'revision_cannot_delete_latest' => '현재 버전본은 지울 수 없습니다.', + 'revision_cannot_delete_latest' => '최신 수정본은 지울 수 없습니다.', // Copy view 'copy_consider' => '항목을 복사할 때 다음을 고려하세요.', diff --git a/lang/ko/settings.php b/lang/ko/settings.php index 1138b0ece8e..0488bfe140e 100644 --- a/lang/ko/settings.php +++ b/lang/ko/settings.php @@ -18,13 +18,13 @@ 'app_name' => '애플리케이션 이름 (사이트 제목)', 'app_name_desc' => '이 이름은 헤더와 시스템에서 보낸 모든 이메일에 표시됩니다.', 'app_name_header' => '헤더에 이름 표시', - 'app_public_access' => '사이트 공개', - 'app_public_access_desc' => '이 옵션을 활성화하면 로그인하지 않은 방문자도 이 서버의 콘텐츠에 액세스할 수 있습니다.', + 'app_public_access' => '공개 접근', + 'app_public_access_desc' => '이 옵션을 활성화하면 로그인하지 않은 방문자가 BookStack 인스턴스의 내용에 접근할 수 있습니다.', 'app_public_access_desc_guest' => '일반 방문자의 액세스는 "Guest" 사용자를 통해 제어할 수 있습니다.', 'app_public_access_toggle' => '공개 액세스 허용', 'app_public_viewing' => '공개 열람을 허용할까요?', - 'app_secure_images' => '보안 강화된 이미지 업로드', - 'app_secure_images_toggle' => '보안 강화된 이미지 업로드 사용', + 'app_secure_images' => '보안을 강화하여 이미지 올려두기', + 'app_secure_images_toggle' => '보안을 강화하여 이미지 올려두기 활성화', 'app_secure_images_desc' => '성능상의 이유로 모든 이미지는 공개됩니다. 이 옵션은 이미지 URL 앞에 추측하기 어려운 임의의 문자열을 추가합니다. 쉽게 액세스할 수 없도록 디렉토리 인덱스가 활성화되어 있지 않은지 확인하세요.', 'app_default_editor' => '기본 페이지 편집기', 'app_default_editor_desc' => '새 페이지를 편집할 때 기본으로 사용될 편집기를 선택합니다. 권한을 갖고 있다면 페이지마다 다르게 적용될 수 있습니다.', @@ -157,7 +157,7 @@ 'audit_event_filter_no_filter' => '필터 없음', 'audit_deleted_item' => '삭제한 항목', 'audit_deleted_item_name' => '이름: :name', - 'audit_table_user' => '사용자', + 'audit_table_user' => '이용자', 'audit_table_event' => '이벤트', 'audit_table_related' => '관련 항목 또는 세부 사항', 'audit_table_ip' => 'IP 주소', @@ -167,7 +167,7 @@ // Role Settings 'roles' => '역할', - 'role_user_roles' => '사용자 역할', + 'role_user_roles' => '이용자 역할', 'roles_index_desc' => '역할은 사용자를 그룹화하고 구성원에게 시스템 권한을 제공하기 위해 사용됩니다. 사용자가 여러 역할의 구성원인 경우 부여된 권한이 중첩되며 모든 권한을 상속받게 됩니다.', 'roles_x_users_assigned' => ':count 명의 사용자가 할당됨|:count 명의 사용자가 할당됨', 'roles_x_permissions_provided' => ':count 개의 권한|:count 개의 권한', @@ -186,7 +186,7 @@ 'role_mfa_enforced' => '다중 인증 필요', 'role_external_auth_id' => '외부 인증 계정', 'role_system' => '시스템 권한', - 'role_manage_users' => '사용자 관리', + 'role_manage_users' => '이용자 관리하기', 'role_manage_roles' => '권한 관리', 'role_manage_entity_permissions' => '문서별 권한 관리', 'role_manage_own_entity_permissions' => '직접 만든 문서별 권한 관리', @@ -217,7 +217,7 @@ 'user_profile' => '사용자 프로필', 'users_add_new' => '사용자 만들기', 'users_search' => '사용자 검색', - 'users_latest_activity' => '마지막 활동', + 'users_latest_activity' => '최근 활동', 'users_details' => '사용자 정보', 'users_details_desc' => '메일 주소로 로그인합니다.', 'users_details_desc_no_email' => '사용자 이름을 바꿉니다.', diff --git a/lang/nb/common.php b/lang/nb/common.php index 38104ba0396..f4f804e4478 100644 --- a/lang/nb/common.php +++ b/lang/nb/common.php @@ -30,8 +30,8 @@ 'create' => 'Opprett', 'update' => 'Oppdater', 'edit' => 'Rediger', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'Arkiver', + 'unarchive' => 'Av-arkiver', 'sort' => 'Sortér', 'move' => 'Flytt', 'copy' => 'Kopier', diff --git a/lang/nb/entities.php b/lang/nb/entities.php index 50f3847d867..cf0fdffded0 100644 --- a/lang/nb/entities.php +++ b/lang/nb/entities.php @@ -63,10 +63,10 @@ 'import_delete_desc' => 'Dette vil slette den opplastede importen av ZIP-filen og kan ikke angres.', 'import_errors' => 'Import feil', 'import_errors_desc' => 'Feil oppstod under importforsøket:', - 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', - 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter', - 'breadcrumb_siblings_for_book' => 'Navigate siblings for book', - 'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf', + 'breadcrumb_siblings_for_page' => 'Naviger relaterte sider', + 'breadcrumb_siblings_for_chapter' => 'Naviger relaterte kapitler', + 'breadcrumb_siblings_for_book' => 'Naviger relaterte bøker', + 'breadcrumb_siblings_for_bookshelf' => 'Naviger relaterte hyller', // Permissions and restrictions 'permissions' => 'Tilganger', @@ -252,7 +252,7 @@ 'pages_edit_switch_to_markdown_stable' => '(Urørt innhold)', 'pages_edit_switch_to_wysiwyg' => 'Bytt til WYSIWYG tekstredigering', 'pages_edit_switch_to_new_wysiwyg' => 'Bytt til ny WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(under Beta-testing)', 'pages_edit_set_changelog' => 'Angi endringslogg', 'pages_edit_enter_changelog_desc' => 'Gi en kort beskrivelse av endringene dine', 'pages_edit_enter_changelog' => 'Se endringslogg', @@ -272,7 +272,7 @@ 'pages_md_insert_drawing' => 'Sett inn tegning', 'pages_md_show_preview' => 'Forhåndsvisning', 'pages_md_sync_scroll' => 'Synkroniser forhåndsvisningsrulle', - 'pages_md_plain_editor' => 'Plaintext editor', + 'pages_md_plain_editor' => 'Redigeringsverktøy for klartekst', 'pages_drawing_unsaved' => 'Ulagret tegning funnet', 'pages_drawing_unsaved_confirm' => 'Ulagret tegningsdata ble funnet fra en tidligere mislykket lagring. Vil du gjenopprette og fortsette å redigere denne ulagrede tegningen?', 'pages_not_in_chapter' => 'Siden tilhører ingen kapittel', @@ -397,11 +397,11 @@ 'comment' => 'Kommentar', 'comments' => 'Kommentarer', 'comment_add' => 'Skriv kommentar', - 'comment_none' => 'No comments to display', + 'comment_none' => 'Ingen kommentarer å vise', 'comment_placeholder' => 'Skriv en kommentar her', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', + 'comment_thread_count' => ':count Kommentar Tråd|:count Kommentar Tråder', + 'comment_archived_count' => ':count Arkivert', + 'comment_archived_threads' => 'Arkiverte tråder', 'comment_save' => 'Publiser kommentar', 'comment_new' => 'Ny kommentar', 'comment_created' => 'kommenterte :createDiff', @@ -410,14 +410,14 @@ 'comment_deleted_success' => 'Kommentar fjernet', 'comment_created_success' => 'Kommentar skrevet', 'comment_updated_success' => 'Kommentar endret', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', + 'comment_archive_success' => 'Kommentar arkivert', + 'comment_unarchive_success' => 'Kommentar uarkivert', + 'comment_view' => 'Vis kommentar', + 'comment_jump_to_thread' => 'Gå til tråd', 'comment_delete_confirm' => 'Er du sikker på at du vil fjerne kommentaren?', 'comment_in_reply_to' => 'Som svar til :commentId', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', + 'comment_reference' => 'Referanse', + 'comment_reference_outdated' => '(Utdatert)', 'comment_editor_explain' => 'Her er kommentarene som er på denne siden. Kommentarer kan legges til og administreres når du ser på den lagrede siden.', // Revision diff --git a/lang/nb/errors.php b/lang/nb/errors.php index ef68da4fcbe..91368314c98 100644 --- a/lang/nb/errors.php +++ b/lang/nb/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => 'Kunne ikke lese ZIP-filen.', 'import_zip_cant_decode_data' => 'Kunne ikke finne og dekode ZIP data.json innhold.', 'import_zip_no_data' => 'ZIP-fildata har ingen forventet bok, kapittel eller sideinnhold.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'ZIP data.json innholdet overskrider maksimal filstørrelse for opplasting.', 'import_validation_failed' => 'Import av ZIP feilet i å validere med feil:', 'import_zip_failed_notification' => 'Kunne ikke importere ZIP-fil.', 'import_perms_books' => 'Du mangler nødvendige tillatelser for å lage bøker.', diff --git a/lang/nb/notifications.php b/lang/nb/notifications.php index 1e16229f31f..47dc34a7150 100644 --- a/lang/nb/notifications.php +++ b/lang/nb/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => 'Oppdatert side: :pageName', 'updated_page_intro' => 'En side er oppdatert i :appName:', 'updated_page_debounce' => 'For å forhindre mange varslinger, vil du ikke få nye varslinger for endringer på denne siden fra samme forfatter.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'Du har blitt nevnt i en kommentar på siden: :pageName', + 'comment_mention_intro' => 'Du har blitt nevnt i en kommentar på :appName:', 'detail_page_name' => 'Sidenavn:', 'detail_page_path' => 'Side bane:', diff --git a/lang/nb/preferences.php b/lang/nb/preferences.php index 6a6850b8371..b41675caf89 100644 --- a/lang/nb/preferences.php +++ b/lang/nb/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'Kontroller e-postvarslene du mottar når en bestemt aktivitet utføres i systemet.', 'notifications_opt_own_page_changes' => 'Varsle ved endringer til sider jeg eier', 'notifications_opt_own_page_comments' => 'Varsle om kommentarer på sider jeg eier', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'Varsle når jeg blir nevnt i en kommentar', 'notifications_opt_comment_replies' => 'Varsle ved svar på mine kommentarer', 'notifications_save' => 'Lagre innstillinger', 'notifications_update_success' => 'Varslingsinnstillingene er oppdatert!', diff --git a/lang/nb/settings.php b/lang/nb/settings.php index d1f40814edf..61b1c33671c 100644 --- a/lang/nb/settings.php +++ b/lang/nb/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Ingen begrensninger er satt', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', + 'sorting' => 'Lister & Sortering', + 'sorting_book_default' => 'Standard regel for boksortering', 'sorting_book_default_desc' => 'Velg standard sorteringsregelen som skal brukes for nye bøker. Dette vil ikke påvirke eksisterende bøker, og kan overstyres per bok.', 'sorting_rules' => 'Sorteringsregler', 'sorting_rules_desc' => 'Dette er forhåndsdefinerte sorteringsoperasjoner som kan brukes på innhold i systemet.', @@ -91,20 +91,20 @@ 'sort_rule_details_desc' => 'Angi et navn for denne sorteringsregelen, som vil vises i lister når brukerne velger en sorteringsmetode.', 'sort_rule_operations' => 'Sorteringsoperasjoner', 'sort_rule_operations_desc' => 'Konfigurer sorteringshandlinger ved å flytte dem fra listen over tilgjengelige operasjoner. Ved bruk vil operasjonene bli brukt i rekkefølge, fra topp til bunn. Eventuelle endringer gjort her vil bli brukt for alle tildelte bøker når du lagrer.', - 'sort_rule_available_operations' => 'Available Operations', - 'sort_rule_available_operations_empty' => 'No operations remaining', - 'sort_rule_configured_operations' => 'Configured Operations', + 'sort_rule_available_operations' => 'Tilgjengelige operasjoner', + 'sort_rule_available_operations_empty' => 'Ingen gjenværende operasjoner', + 'sort_rule_configured_operations' => 'Konfigurerte operasjoner', 'sort_rule_configured_operations_empty' => 'Dra/legg til operasjoner fra listen "Tilgjengelige operasjoner"', - 'sort_rule_op_asc' => '(Asc)', - 'sort_rule_op_desc' => '(Desc)', + 'sort_rule_op_asc' => '(Stigende)', + 'sort_rule_op_desc' => '(Synkende)', 'sort_rule_op_name' => 'Navn - Alfabetisk', - 'sort_rule_op_name_numeric' => 'Name - Numeric', - 'sort_rule_op_created_date' => 'Created Date', - 'sort_rule_op_updated_date' => 'Updated Date', + 'sort_rule_op_name_numeric' => 'Navn - Numerisk', + 'sort_rule_op_created_date' => 'Dato opprettet', + 'sort_rule_op_updated_date' => 'Dato oppdatert', 'sort_rule_op_chapters_first' => 'Kapitler først', 'sort_rule_op_chapters_last' => 'Kapitler sist', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits' => 'Visningsgrenser for hver side', + 'sorting_page_limits_desc' => 'Angi hvor mange elementer som skal vises på hver side i ulike lister i systemet. Et lavere antall vil vanligvis gi bedre ytelse, mens et høyere antall reduserer behovet for å bla gjennom mange sider. Det er anbefalt å bruke en multiplikasjon av 3 som gir partall (18, 24, 30 osv.).', // Maintenance settings 'maint' => 'Vedlikehold', @@ -197,13 +197,13 @@ 'role_import_content' => 'Import innhold', 'role_editor_change' => 'Endre sideredigering', 'role_notifications' => 'Motta og administrere varslinger', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => 'Disse tillatelsene vil teknisk sett også gi mulighet til å se & søke etter brukere & roller i systemet.', 'role_asset' => 'Eiendomstillatelser', 'roles_system_warning' => 'Vær oppmerksom på at tilgang til noen av de ovennevnte tre tillatelsene kan tillate en bruker å endre sine egne rettigheter eller rettighetene til andre i systemet. Bare tildel roller med disse tillatelsene til pålitelige brukere.', 'role_asset_desc' => 'Disse tillatelsene kontrollerer standard tilgang til eiendelene i systemet. Tillatelser til bøker, kapitler og sider overstyrer disse tillatelsene.', 'role_asset_admins' => 'Administratorer får automatisk tilgang til alt innhold, men disse alternativene kan vise eller skjule UI-alternativer.', 'role_asset_image_view_note' => 'Dette gjelder synlighet innenfor bilde-administrasjonen. Faktisk tilgang på opplastede bildefiler vil være avhengig av valget for systemlagring av bildet.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Disse tillatelsene vil teknisk sett også gi mulighet til å se & søke etter brukere i systemet.', 'role_all' => 'Alle', 'role_own' => 'Egne', 'role_controlled_by_asset' => 'Kontrollert av eiendelen de er lastet opp til', diff --git a/lang/nb/validation.php b/lang/nb/validation.php index 27922e4020e..09f25d0459d 100644 --- a/lang/nb/validation.php +++ b/lang/nb/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'kunne ikke lastes opp, tjeneren støtter ikke filer av denne størrelsen.', 'zip_file' => 'Attributtet :attribute må henvises til en fil i ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => 'Filen :attribute må ikke overstige :size MB.', 'zip_file_mime' => 'Attributtet :attribute må referere en fil av typen :validTypes, som ble funnet :foundType.', 'zip_model_expected' => 'Data objekt forventet, men ":type" funnet.', 'zip_unique' => 'Attributtet :attribute må være unikt for objekttypen i ZIP.', diff --git a/lang/nl/errors.php b/lang/nl/errors.php index c2a666546ef..c1de7b36e8c 100644 --- a/lang/nl/errors.php +++ b/lang/nl/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => 'Kon het Zip-bestand niet lezen.', 'import_zip_cant_decode_data' => 'Kon de data.json Zip-inhoud niet vinden of decoderen.', 'import_zip_no_data' => 'Zip-bestand bevat niet de verwachte boek, hoofdstuk of pagina-inhoud.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'De inhoud van data.json in de ZIP overschrijdt de ingestelde maximum upload grootte.', 'import_validation_failed' => 'De validatie van het Zip-bestand is mislukt met de volgende fouten:', 'import_zip_failed_notification' => 'Importeren van het Zip-bestand is mislukt.', 'import_perms_books' => 'Je mist de vereiste machtigingen om boeken te maken.', diff --git a/lang/nl/notifications.php b/lang/nl/notifications.php index ce2a3ebff02..cccd1cca2a2 100644 --- a/lang/nl/notifications.php +++ b/lang/nl/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => 'Aangepaste pagina: :pageName', 'updated_page_intro' => 'Een pagina werd aangepast in :appName:', 'updated_page_debounce' => 'Om een stortvloed aan meldingen te voorkomen, zul je een tijdje geen meldingen ontvangen voor verdere bewerkingen van deze pagina door dezelfde redacteur.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'Je bent vermeld in een opmerking op pagina: :pageName', + 'comment_mention_intro' => 'Je bent vermeld in een opmerking in :appName:', 'detail_page_name' => 'Pagina Naam:', 'detail_page_path' => 'Paginapad:', diff --git a/lang/nl/preferences.php b/lang/nl/preferences.php index 1fb56af074e..a4a229f72d3 100644 --- a/lang/nl/preferences.php +++ b/lang/nl/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'Bepaal welke e-mailmeldingen je ontvangt wanneer bepaalde activiteiten in het systeem worden uitgevoerd.', 'notifications_opt_own_page_changes' => 'Geef melding bij wijzigingen aan pagina\'s waarvan ik de eigenaar ben', 'notifications_opt_own_page_comments' => 'Geef melding van opmerkingen op pagina\'s waarvan ik de eigenaar ben', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'Geef een melding wanneer ik word vermeld in een opmerking', 'notifications_opt_comment_replies' => 'Geef melding van reacties op mijn opmerkingen', 'notifications_save' => 'Voorkeuren opslaan', 'notifications_update_success' => 'Voorkeuren voor meldingen zijn bijgewerkt!', diff --git a/lang/nl/settings.php b/lang/nl/settings.php index 2041669eb12..63805f4983c 100644 --- a/lang/nl/settings.php +++ b/lang/nl/settings.php @@ -197,13 +197,13 @@ 'role_import_content' => 'Importeer inhoud', 'role_editor_change' => 'Wijzig pagina bewerker', 'role_notifications' => 'Meldingen ontvangen & beheren', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => 'Deze machtigingen geven technisch gezien toegang tot het weergeven van gebruikers & rollen binnen het systeem.', 'role_asset' => 'Asset Machtigingen', 'roles_system_warning' => 'Wees ervan bewust dat toegang tot een van de bovengenoemde drie machtigingen een gebruiker in staat kan stellen zijn eigen machtigingen of de machtigingen van anderen in het systeem kan wijzigen. Wijs alleen rollen toe met deze machtigingen aan vertrouwde gebruikers.', 'role_asset_desc' => 'Deze machtigingen bepalen de standaard toegang tot de assets binnen het systeem. Machtigingen op boeken, hoofdstukken en pagina\'s overschrijven deze instelling.', 'role_asset_admins' => 'Beheerders krijgen automatisch toegang tot alle inhoud, maar deze opties kunnen gebruikersinterface opties tonen of verbergen.', 'role_asset_image_view_note' => 'Dit heeft betrekking op de zichtbaarheid binnen de afbeeldingsbeheerder. De werkelijke toegang tot geüploade afbeeldingsbestanden hangt af van de gekozen opslagmethode.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Deze machtigingen geven technisch gezien toegang tot het weergeven van gebruikers binnen het systeem.', 'role_all' => 'Alles', 'role_own' => 'Eigen', 'role_controlled_by_asset' => 'Gecontroleerd door de asset waar deze is geüpload', diff --git a/lang/nl/validation.php b/lang/nl/validation.php index e2f48e31a63..f5088735dd7 100644 --- a/lang/nl/validation.php +++ b/lang/nl/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'Het bestand kon niet worden geüpload. De server accepteert mogelijk geen bestanden van deze grootte.', 'zip_file' => 'Het \':attribute\' veld moet verwijzen naar een bestand in de ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => 'Het bestand :attribute mag niet groter zijn dan :size MB.', 'zip_file_mime' => 'Het \':attribute\' veld moet verwijzen naar een bestand met het type :validTypes, vond :foundType.', 'zip_model_expected' => 'Dataobject verwacht maar vond ":type".', 'zip_unique' => ':attribute moet uniek zijn voor het objecttype binnen de ZIP.', diff --git a/lang/uk/errors.php b/lang/uk/errors.php index 40c0d90146d..2d32276a399 100644 --- a/lang/uk/errors.php +++ b/lang/uk/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => 'Не вдалося прочитати ZIP-файл.', 'import_zip_cant_decode_data' => 'Не вдалося знайти і розшифрувати контент ZIP data.json.', 'import_zip_no_data' => 'ZIP-файл не містить очікуваної книги, глави або вмісту сторінки.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'Вміст ZIP data.json перевищує налаштований максимальний розмір додатка.', 'import_validation_failed' => 'Не вдалося виконати перевірку ZIP-адреси із помилками:', 'import_zip_failed_notification' => 'Не вдалося імпортувати ZIP-файл.', 'import_perms_books' => 'У Вас не вистачає необхідних прав для створення книг.', diff --git a/lang/uk/notifications.php b/lang/uk/notifications.php index d40457f98fc..896808c7574 100644 --- a/lang/uk/notifications.php +++ b/lang/uk/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => 'Оновлено сторінку: :pageName', 'updated_page_intro' => 'Оновлено сторінку у :appName:', 'updated_page_debounce' => 'Для запобігання кількості сповіщень, деякий час ви не будете відправлені повідомлення для подальших змін на цій сторінці тим самим редактором.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'Вас згадали в коментарях на сторінці: :pageName', + 'comment_mention_intro' => 'Вас згадали в коментарі до :appName:', 'detail_page_name' => 'Назва сторінки:', 'detail_page_path' => 'Шлях до сторінки:', diff --git a/lang/uk/preferences.php b/lang/uk/preferences.php index 8af3a8d9e55..149cf320527 100644 --- a/lang/uk/preferences.php +++ b/lang/uk/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'Контролюйте сповіщення по електронній пошті, які ви отримуєте, коли виконується певна активність у системі.', 'notifications_opt_own_page_changes' => 'Повідомляти при змінах сторінок якими я володію', 'notifications_opt_own_page_comments' => 'Повідомляти при коментарях на моїх сторінках', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'Сповіщати, якщо мене згадали у коментарі', 'notifications_opt_comment_replies' => 'Повідомляти про відповіді на мої коментарі', 'notifications_save' => 'Зберегти налаштування', 'notifications_update_success' => 'Налаштування сповіщень було оновлено!', diff --git a/lang/uk/settings.php b/lang/uk/settings.php index 633582ca834..55966c01c57 100644 --- a/lang/uk/settings.php +++ b/lang/uk/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Не встановлено обмежень', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', + 'sorting' => 'Списки і сортування', + 'sorting_book_default' => 'Типовий порядок сортування книги', 'sorting_book_default_desc' => 'Виберіть правило сортування за замовчуванням для застосування нових книг. Це не вплине на існуючі книги, і може бути перевизначено для кожної книги.', 'sorting_rules' => 'Сортувати правила', 'sorting_rules_desc' => 'Це попередньо визначені операції сортування, які можуть бути застосовані до вмісту в системі.', @@ -103,8 +103,8 @@ 'sort_rule_op_updated_date' => 'Дата оновлення', 'sort_rule_op_chapters_first' => 'Спочатку розділи', 'sort_rule_op_chapters_last' => 'Розділи останні', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits' => 'Обмеження відображення сторінок', + 'sorting_page_limits_desc' => 'Кількість елементів для відображення в різних списках в системі. Зазвичай менша кількість буде більш продуктивною, в той час як більша кількість уникає необхідність натискання на кілька сторінок. Рекомендується використовувати парне кратне 3 (18, 24, 30 тощо).', // Maintenance settings 'maint' => 'Обслуговування', @@ -197,13 +197,13 @@ 'role_import_content' => 'Імпортувати вміст', 'role_editor_change' => 'Змінити редактор сторінок', 'role_notifications' => 'Отримувати та керувати повідомленнями', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => 'Ці дозволи технічно також забезпечать видимість і пошук ролей у системі.', 'role_asset' => 'Дозволи', 'roles_system_warning' => 'Майте на увазі, що доступ до будь-якого з вищезазначених трьох дозволів може дозволити користувачеві змінювати власні привілеї або привілеї інших в системі. Ролі з цими дозволами призначайте лише довіреним користувачам.', 'role_asset_desc' => 'Ці дозволи контролюють стандартні доступи всередині системи. Права на книги, розділи та сторінки перевизначать ці дозволи.', 'role_asset_admins' => 'Адміністратори автоматично отримують доступ до всього вмісту, але ці параметри можуть відображати або приховувати параметри інтерфейсу користувача.', 'role_asset_image_view_note' => 'Це стосується видимості в менеджері зображень. Фактичний доступ завантажуваних зображень буде залежний від опції зберігання системних зображень.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Ці дозволи технічно також забезпечать видимість і пошук користувачів і ролей у системі.', 'role_all' => 'Все', 'role_own' => 'Власне', 'role_controlled_by_asset' => 'Контролюється за об\'єктом, до якого вони завантажуються', diff --git a/lang/uk/validation.php b/lang/uk/validation.php index 79aa5070200..22f27b6ccd1 100644 --- a/lang/uk/validation.php +++ b/lang/uk/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'Не вдалося завантажити файл. Сервер може не приймати файли такого розміру.', 'zip_file' => 'Поле :attribute повинне вказувати файл в ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => 'Файл :attribute не повинен перевищувати :size МБ.', 'zip_file_mime' => 'Поле :attribute повинне посилатись на файл типу :validtypes, знайдений :foundType.', 'zip_model_expected' => 'Очікувався об’єкт даних, але знайдено ":type".', 'zip_unique' => 'Поле :attribute має бути унікальним для типу об\'єкта в ZIP.', diff --git a/lang/uz/auth.php b/lang/uz/auth.php index 1a22f0f1d33..59a620daeca 100644 --- a/lang/uz/auth.php +++ b/lang/uz/auth.php @@ -91,7 +91,7 @@ 'mfa_option_totp_title' => 'Mobil ilova', 'mfa_option_totp_desc' => 'Ko‘p faktorli autentifikatsiyadan foydalanish uchun sizga Google Authenticator, Authy yoki Microsoft Authenticator kabi OTPni qo‘llab-quvvatlaydigan mobil ilova kerak bo‘ladi.', 'mfa_option_backup_codes_title' => 'Zaxira kodlari', - 'mfa_option_backup_codes_desc' => 'Generates a set of one-time-use backup codes which you\'ll enter on login to verify your identity. Make sure to store these in a safe & secure place.', + 'mfa_option_backup_codes_desc' => 'Shaxsingizni tasdiqlash uchun tizimga kirishda kiritadigan bir martalik zaxira kodlari to\'plamini yaratadi. Bularni xavfsiz va ishonchli joyda saqlang.', 'mfa_gen_confirm_and_enable' => 'Tasdiqlash va yoqish', 'mfa_gen_backup_codes_title' => 'Zaxira kodlarini sozlash', 'mfa_gen_backup_codes_desc' => 'Quyidagi kodlar ro‘yxatini xavfsiz joyda saqlang. Tizimga kirishda siz kodlardan birini ikkinchi autentifikatsiya mexanizmi sifatida ishlatishingiz mumkin.', diff --git a/lang/uz/validation.php b/lang/uz/validation.php index 600195587c0..a8c12b1b8b1 100644 --- a/lang/uz/validation.php +++ b/lang/uz/validation.php @@ -105,11 +105,11 @@ 'url' => ':attribute URL formatida emas.', 'uploaded' => 'Faylni yuklashda xatolik. Server bunday hajmdagi faylllarni yuklamasligi mumkin.', - 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', - 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', - 'zip_model_expected' => 'Data object expected but ":type" found.', - 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', + 'zip_file' => ':attribute ZIP ichidagi faylga havola qilishi kerak.', + 'zip_file_size' => ':attribute fayli :size MB dan oshmasligi kerak.', + 'zip_file_mime' => ':attribute :validTypes turidagi faylga havola qilishi kerak, lekin :foundType turida keldi.', + 'zip_model_expected' => 'Ma\'lumotlar obyekti kutilmoqda, ammo ":type" topildi.', + 'zip_unique' => ':attribute ZIP ichidagi obyekt turi uchun noyob bo\'lishi kerak.', // Custom validation lines 'custom' => [ diff --git a/lang/zh_CN/activities.php b/lang/zh_CN/activities.php index 08f831f6102..a2a2acd24f2 100644 --- a/lang/zh_CN/activities.php +++ b/lang/zh_CN/activities.php @@ -28,22 +28,22 @@ 'chapter_move_notification' => '章节移动成功', // Books - 'book_create' => '图书已创建', - 'book_create_notification' => '成功创建图书', - 'book_create_from_chapter' => '将章节转换为图书', - 'book_create_from_chapter_notification' => '章节已成功转换为图书', - 'book_update' => '图书已更新', - 'book_update_notification' => '图书更新成功', - 'book_delete' => '图书已删除', - 'book_delete_notification' => '图书删除成功', - 'book_sort' => '图书已排序', - 'book_sort_notification' => '图书重新排序成功', + 'book_create' => '书籍已创建', + 'book_create_notification' => '成功创建书籍', + 'book_create_from_chapter' => '将章节转换为书籍', + 'book_create_from_chapter_notification' => '章节已成功转换为书籍', + 'book_update' => '书籍已更新', + 'book_update_notification' => '书籍更新成功', + 'book_delete' => '书籍已删除', + 'book_delete_notification' => '书籍删除成功', + 'book_sort' => '书籍已排序', + 'book_sort_notification' => '书籍重新排序成功', // Bookshelves 'bookshelf_create' => '书架已创建', 'bookshelf_create_notification' => '书架创建成功', - 'bookshelf_create_from_book' => '将图书转换为书架', - 'bookshelf_create_from_book_notification' => '图书已成功转换为书架', + 'bookshelf_create_from_book' => '将书籍转换为书架', + 'bookshelf_create_from_book_notification' => '书籍已成功转换为书架', 'bookshelf_update' => '书架已更新', 'bookshelf_update_notification' => '书架更新成功', 'bookshelf_delete' => '书架已删除', diff --git a/lang/zh_CN/entities.php b/lang/zh_CN/entities.php index 03f6393ca88..826a8ec1e65 100644 --- a/lang/zh_CN/entities.php +++ b/lang/zh_CN/entities.php @@ -121,79 +121,79 @@ 'shelves_save' => '保存书架', 'shelves_books' => '书籍已在此书架里', 'shelves_add_books' => '将书籍加入此书架', - 'shelves_drag_books' => '拖动下面的图书将其添加到此书架', - 'shelves_empty_contents' => '这个书架没有分配图书', - 'shelves_edit_and_assign' => '编辑书架以分配图书', + 'shelves_drag_books' => '拖动下面的书籍将其添加到此书架', + 'shelves_empty_contents' => '这个书架没有分配书籍', + 'shelves_edit_and_assign' => '编辑书架以分配书籍', 'shelves_edit_named' => '编辑书架 :name', 'shelves_edit' => '编辑书架', 'shelves_delete' => '删除书架', 'shelves_delete_named' => '删除书架 :name', - 'shelves_delete_explain' => "此操作将删除书架 ”:name”。书架中的图书不会被删除。", + 'shelves_delete_explain' => "此操作将删除书架 ”:name”。书架中的书籍不会被删除。", 'shelves_delete_confirmation' => '您确定要删除此书架吗?', 'shelves_permissions' => '书架权限', 'shelves_permissions_updated' => '书架权限已更新', 'shelves_permissions_active' => '书架权限已启用', - 'shelves_permissions_cascade_warning' => '书架上的权限不会自动应用到书架里的图书上,这是因为图书可以在多个书架上存在。使用下面的选项可以将权限复制到书架里的图书上。', - 'shelves_permissions_create' => '书架创建权限仅用于使用下面的操作将权限复制到子图书。这个权限不是用来控制创建书籍的。', - 'shelves_copy_permissions_to_books' => '将权限复制到图书', + 'shelves_permissions_cascade_warning' => '书架上的权限不会自动应用到书架里的书籍上,这是因为书籍可以在多个书架上存在。使用下面的选项可以将权限复制到书架里的书籍上。', + 'shelves_permissions_create' => '书架创建权限仅用于使用下面的操作将权限复制到子书籍。这个权限不是用来控制创建书籍的。', + 'shelves_copy_permissions_to_books' => '将权限复制到书籍', 'shelves_copy_permissions' => '复制权限', - 'shelves_copy_permissions_explain' => '此操作会将此书架的当前权限设置应用于其中包含的所有图书上。 启用前请确保已保存对此书架权限的任何更改。', - 'shelves_copy_permission_success' => '书架权限已复制到 :count 本图书上', + 'shelves_copy_permissions_explain' => '此操作会将此书架的当前权限设置应用于其中包含的所有书籍上。 启用前请确保已保存对此书架权限的任何更改。', + 'shelves_copy_permission_success' => '书架权限已复制到 :count 本书籍上', // Books - 'book' => '图书', - 'books' => '图书', + 'book' => '书籍', + 'books' => '书籍', 'x_books' => ':count 本书', 'books_empty' => '不存在已创建的书', - 'books_popular' => '热门图书', + 'books_popular' => '热门书籍', 'books_recent' => '最近的书', 'books_new' => '新书', 'books_new_action' => '新书', - 'books_popular_empty' => '最受欢迎的图书将出现在这里。', - 'books_new_empty' => '最近创建的图书将出现在这里。', - 'books_create' => '创建图书', - 'books_delete' => '删除图书', - 'books_delete_named' => '删除图书「:bookName」', - 'books_delete_explain' => '此操作将删除图书 “:bookName”。图书中的所有的章节和页面都会被删除。', - 'books_delete_confirmation' => '您确定要删除此图书吗?', - 'books_edit' => '编辑图书', - 'books_edit_named' => '编辑图书「:bookName」', + 'books_popular_empty' => '最受欢迎的书籍将出现在这里。', + 'books_new_empty' => '最近创建的书籍将出现在这里。', + 'books_create' => '创建书籍', + 'books_delete' => '删除书籍', + 'books_delete_named' => '删除书籍「:bookName」', + 'books_delete_explain' => '此操作将删除书籍 “:bookName”。书籍中的所有的章节和页面都会被删除。', + 'books_delete_confirmation' => '您确定要删除此书籍吗?', + 'books_edit' => '编辑书籍', + 'books_edit_named' => '编辑书籍「:bookName」', 'books_form_book_name' => '书名', - 'books_save' => '保存图书', - 'books_permissions' => '图书权限', - 'books_permissions_updated' => '图书权限已更新', + 'books_save' => '保存书籍', + 'books_permissions' => '书籍权限', + 'books_permissions_updated' => '书籍权限已更新', 'books_empty_contents' => '本书目前没有页面或章节。', 'books_empty_create_page' => '创建页面', - 'books_empty_sort_current_book' => '排序当前图书', + 'books_empty_sort_current_book' => '排序当前书籍', 'books_empty_add_chapter' => '添加章节', - 'books_permissions_active' => '图书权限已启用', + 'books_permissions_active' => '书籍权限已启用', 'books_search_this' => '搜索这本书', - 'books_navigation' => '图书导航', - 'books_sort' => '排序图书内容', + 'books_navigation' => '书籍导航', + 'books_sort' => '排序书籍内容', 'books_sort_desc' => '在书籍内部移动章节与页面以重组内容;支持添加其他书籍,实现跨书籍便捷移动章节与页面;还可设置自动排序规则,在内容发生变更时自动对本书内容进行排序。', 'books_sort_auto_sort' => '自动排序选项', 'books_sort_auto_sort_active' => '自动排序已激活:::sortName', - 'books_sort_named' => '排序图书「:bookName」', + 'books_sort_named' => '排序书籍「:bookName」', 'books_sort_name' => '按名称排序', 'books_sort_created' => '创建时间排序', 'books_sort_updated' => '按更新时间排序', 'books_sort_chapters_first' => '章节正序', 'books_sort_chapters_last' => '章节倒序', - 'books_sort_show_other' => '显示其他图书', + 'books_sort_show_other' => '显示其他书籍', 'books_sort_save' => '保存新顺序', - 'books_sort_show_other_desc' => '在此添加其他图书进入排序界面,这样就可以轻松跨图书重新排序。', + 'books_sort_show_other_desc' => '在此添加其他书籍进入排序界面,这样就可以轻松跨书籍重新排序。', 'books_sort_move_up' => '上移', 'books_sort_move_down' => '下移', - 'books_sort_move_prev_book' => '移动到上一图书', - 'books_sort_move_next_book' => '移动到下一图书', + 'books_sort_move_prev_book' => '移动到上一书籍', + 'books_sort_move_next_book' => '移动到下一书籍', 'books_sort_move_prev_chapter' => '移动到上一章节', 'books_sort_move_next_chapter' => '移动到下一章节', - 'books_sort_move_book_start' => '移动到图书开头', - 'books_sort_move_book_end' => '移动到图书结尾', + 'books_sort_move_book_start' => '移动到书籍开头', + 'books_sort_move_book_end' => '移动到书籍结尾', 'books_sort_move_before_chapter' => '移动到章节前', 'books_sort_move_after_chapter' => '移至章节后', - 'books_copy' => '复制图书', - 'books_copy_success' => '图书已成功复制', + 'books_copy' => '复制书籍', + 'books_copy_success' => '书籍已成功复制', // Chapters 'chapter' => '章节', @@ -218,7 +218,7 @@ 'chapters_permissions_active' => '章节权限已启用', 'chapters_permissions_success' => '章节权限已更新', 'chapters_search_this' => '从本章节搜索', - 'chapter_sort_book' => '排序图书', + 'chapter_sort_book' => '排序书籍', // Pages 'page' => '页面', @@ -332,7 +332,7 @@ 'toggle_sidebar' => '切换侧边栏', 'page_tags' => '页面标签', 'chapter_tags' => '章节标签', - 'book_tags' => '图书标签', + 'book_tags' => '书籍标签', 'shelf_tags' => '书架标签', 'tag' => '标签', 'tags' => '标签', @@ -345,13 +345,13 @@ 'tags_usages' => '标签总使用量', 'tags_assigned_pages' => '有这个标签的页面', 'tags_assigned_chapters' => '有这个标签的章节', - 'tags_assigned_books' => '有这个标签的图书', + 'tags_assigned_books' => '有这个标签的书籍', 'tags_assigned_shelves' => '有这个标签的书架', 'tags_x_unique_values' => ':count 个不重复项目', 'tags_all_values' => '所有值', 'tags_view_tags' => '查看标签', 'tags_view_existing_tags' => '查看已有的标签', - 'tags_list_empty_hint' => '您可以在页面编辑器的侧边栏添加标签,或者在编辑图书、章节、书架时添加。', + 'tags_list_empty_hint' => '您可以在页面编辑器的侧边栏添加标签,或者在编辑书籍、章节、书架时添加。', 'attachments' => '附件', 'attachments_explain' => '上传一些文件或附加一些链接显示在您的网页上。这些在页面的侧边栏中可见。', 'attachments_explain_instant_save' => '这里的更改将立即保存。', @@ -390,7 +390,7 @@ 'profile_created_content' => '已创建内容', 'profile_not_created_pages' => ':userName尚未创建任何页面', 'profile_not_created_chapters' => ':userName尚未创建任何章节', - 'profile_not_created_books' => ':userName尚未创建任何图书', + 'profile_not_created_books' => ':userName尚未创建任何书籍', 'profile_not_created_shelves' => ':userName 尚未创建任何书架', // Comments @@ -435,13 +435,13 @@ // Conversions 'convert_to_shelf' => '转换为书架', - 'convert_to_shelf_contents_desc' => '你可以将这本书转换为具有相同内容的新书架。本书中的章节将被转换为图书。如果这本书包含有任何不在章节分类中的页面,那么将会有一本单独的图书包含这些页面,这本书也将成为新书架的一部分。', - 'convert_to_shelf_permissions_desc' => '在这本书上设置的任何权限都将复制到所有未强制执行权限的新书架和新子图书上。请注意,书架上的权限不会像图书那样继承到内容物上。', - 'convert_book' => '转换图书', - 'convert_book_confirm' => '您确定要转换此图书吗?', + 'convert_to_shelf_contents_desc' => '你可以将这本书转换为具有相同内容的新书架。本书中的章节将被转换为书籍。如果这本书包含有任何不在章节分类中的页面,那么将会有一本单独的书籍包含这些页面,这本书也将成为新书架的一部分。', + 'convert_to_shelf_permissions_desc' => '在这本书上设置的任何权限都将复制到所有未强制执行权限的新书架和新子书籍上。请注意,书架上的权限不会像书籍那样继承到内容物上。', + 'convert_book' => '转换书籍', + 'convert_book_confirm' => '您确定要转换此书籍吗?', 'convert_undo_warning' => '这可不能轻易撤消。', - 'convert_to_book' => '转换为图书', - 'convert_to_book_desc' => '您可以将此章节转换为具有相同内容的新图书。此章节中设置的任何权限都将复制到新图书上,但从父图书继承的任何权限都不会被复制,这可能会导致访问控制发生变化。', + 'convert_to_book' => '转换为书籍', + 'convert_to_book_desc' => '您可以将此章节转换为具有相同内容的新书籍。此章节中设置的任何权限都将复制到新书籍上,但从父书籍继承的任何权限都不会被复制,这可能会导致访问控制发生变化。', 'convert_chapter' => '转换章节', 'convert_chapter_confirm' => '您确定要转换此章节吗?', @@ -469,8 +469,8 @@ 'watch_detail_new' => '已关注新页面', 'watch_detail_updates' => '已关注新页面和更新', 'watch_detail_comments' => '已关注新页面、更新和评论', - 'watch_detail_parent_book' => '已关注—继承自父图书', - 'watch_detail_parent_book_ignore' => '已忽略—继承自父图书', + 'watch_detail_parent_book' => '已关注—继承自父书籍', + 'watch_detail_parent_book_ignore' => '已忽略—继承自父书籍', 'watch_detail_parent_chapter' => '已关注—继承自父章节', 'watch_detail_parent_chapter_ignore' => '已忽略—继承自父章节', ]; diff --git a/lang/zh_CN/errors.php b/lang/zh_CN/errors.php index 828e7c10203..dfcd3d61a73 100644 --- a/lang/zh_CN/errors.php +++ b/lang/zh_CN/errors.php @@ -68,11 +68,11 @@ // Entities 'entity_not_found' => '未找到项目', 'bookshelf_not_found' => '未找到书架', - 'book_not_found' => '未找到图书', + 'book_not_found' => '未找到书籍', 'page_not_found' => '未找到页面', 'chapter_not_found' => '未找到章节', 'selected_book_not_found' => '选中的书未找到', - 'selected_book_chapter_not_found' => '未找到所选的图书或章节', + 'selected_book_chapter_not_found' => '未找到所选的书籍或章节', 'guests_cannot_save_drafts' => '访客不能保存草稿', // Users diff --git a/lang/zh_CN/settings.php b/lang/zh_CN/settings.php index 93f3076c595..3469752bfce 100644 --- a/lang/zh_CN/settings.php +++ b/lang/zh_CN/settings.php @@ -55,7 +55,7 @@ 'link_color' => '默认链接颜色', 'content_colors_desc' => '为页面组织层次结构中的所有元素设置颜色。为了便于阅读,建议选择与默认颜色亮度相似的颜色。', 'bookshelf_color' => '书架颜色', - 'book_color' => '图书颜色', + 'book_color' => '书籍颜色', 'chapter_color' => '章节颜色', 'page_color' => '页面颜色', 'page_draft_color' => '页面草稿颜色', @@ -188,8 +188,8 @@ 'role_system' => '系统权限', 'role_manage_users' => '管理用户', 'role_manage_roles' => '管理角色与角色权限', - 'role_manage_entity_permissions' => '管理所有图书、章节和页面的权限', - 'role_manage_own_entity_permissions' => '管理自己的图书、章节和页面的权限', + 'role_manage_entity_permissions' => '管理所有书籍、章节和页面的权限', + 'role_manage_own_entity_permissions' => '管理自己的书籍、章节和页面的权限', 'role_manage_page_templates' => '管理页面模板', 'role_access_api' => '访问系统 API', 'role_manage_settings' => '管理 App 设置', diff --git a/lang/zh_TW/errors.php b/lang/zh_TW/errors.php index e6ef7e020de..f9511dcb068 100644 --- a/lang/zh_TW/errors.php +++ b/lang/zh_TW/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => '無法讀取 ZIP 檔案。', 'import_zip_cant_decode_data' => '無法尋找並解碼 ZIP data.json 內容。', 'import_zip_no_data' => 'ZIP 檔案資料沒有預期的書本、章節或頁面內容。', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'ZIP 檔案 data.json 的內容超過了設定的應用程式最大上傳大小。', 'import_validation_failed' => '匯入 ZIP 驗證失敗,發生錯誤:', 'import_zip_failed_notification' => '匯入 ZIP 檔案失敗。', 'import_perms_books' => '您缺乏建立書本所需的權限。', diff --git a/lang/zh_TW/notifications.php b/lang/zh_TW/notifications.php index f5deae3284a..feacf13b4fd 100644 --- a/lang/zh_TW/notifications.php +++ b/lang/zh_TW/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => '頁面更新::pageName', 'updated_page_intro' => ':appName: 中的一個頁面已被更新', 'updated_page_debounce' => '為了防止出現大量通知,一段時間內您不會收到同一編輯者再次編輯本頁面的通知。', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => '您在以下頁面的留言中被提及::pageName', + 'comment_mention_intro' => '您再在 :appName: 的留言中被提及', 'detail_page_name' => '頁面名稱:', 'detail_page_path' => '頁面路徑:', diff --git a/lang/zh_TW/preferences.php b/lang/zh_TW/preferences.php index 0bfce9d832d..2672cc57fb1 100644 --- a/lang/zh_TW/preferences.php +++ b/lang/zh_TW/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => '控制在系統有特定活動時,是否要接收電子郵件通知', 'notifications_opt_own_page_changes' => '當我的頁面有異動時發送通知', 'notifications_opt_own_page_comments' => '當我的頁面有評論時發送通知', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => '當我在留言中被提及時通知我', 'notifications_opt_comment_replies' => '當我的評論有新的回覆時發送通知', 'notifications_save' => '儲存偏好設定', 'notifications_update_success' => '通知設定已更新', diff --git a/lang/zh_TW/settings.php b/lang/zh_TW/settings.php index 9b5efa09e39..0d5d760dd29 100644 --- a/lang/zh_TW/settings.php +++ b/lang/zh_TW/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => '尚未設定限制', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', + 'sorting' => '清單與排序', + 'sorting_book_default' => '預設書籍排序規則', 'sorting_book_default_desc' => '選取要套用至新書籍的預設排序規則。這不會影響現有書籍,並可按書籍覆寫。', 'sorting_rules' => '排序規則', 'sorting_rules_desc' => '這些是預先定義的排序作業,可套用於系統中的內容。', @@ -103,8 +103,8 @@ 'sort_rule_op_updated_date' => '更新日期', 'sort_rule_op_chapters_first' => '第一章', 'sort_rule_op_chapters_last' => '最後一章', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits' => '每頁顯示限制', + 'sorting_page_limits_desc' => '設定系統內各類清單每頁顯示的項目數量。通常較低的數量能提升效能表現,而較高的數量則可避免使用者需點擊翻閱多頁。建議採用 3 的整數倍數(如 18、24、30 等)。', // Maintenance settings 'maint' => '維護', @@ -198,13 +198,13 @@ 'role_import_content' => '匯入內容', 'role_editor_change' => '重設頁面編輯器', 'role_notifications' => '管理和接收通知', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => '這些權限在技術上亦將提供系統內使用者與角色的能見度及搜尋功能。', 'role_asset' => '資源權限', 'roles_system_warning' => '請注意,有上述三項權限中的任一項的使用者都可以更改自己或系統中其他人的權限。有這些權限的角色只應分配給受信任的使用者。', 'role_asset_desc' => '對系統內資源的預設權限將由這裡的權限控制。若有單獨設定在書本、章節和頁面上的權限,將會覆寫這裡的權限設定。', 'role_asset_admins' => '管理員會自動取得對所有內容的存取權,但這些選項可能會顯示或隱藏使用者介面的選項。', 'role_asset_image_view_note' => '這與圖像管理器中的可見性有關。已經上傳的圖片的實際訪問取決於系統圖像存儲選項。', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => '這些權限在技術上亦將提供系統內使用者的能見度及搜尋功能。', 'role_all' => '全部', 'role_own' => '擁有', 'role_controlled_by_asset' => '依據隸屬的資源來決定', diff --git a/lang/zh_TW/validation.php b/lang/zh_TW/validation.php index e6004c59d87..c0c480b8ea6 100644 --- a/lang/zh_TW/validation.php +++ b/lang/zh_TW/validation.php @@ -106,7 +106,7 @@ 'uploaded' => '無法上傳文檔案, 伺服器可能不接受此大小的檔案。', 'zip_file' => ':attribute 需要參照 ZIP 中的檔案。', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => '檔案 :attribute 不能超過 :size MB。', 'zip_file_mime' => ':attribute 需要參照類型為 :validTypes 的檔案,找到 :foundType。', 'zip_model_expected' => '預期為資料物件,但找到「:type」。', 'zip_unique' => '對於 ZIP 中的物件類型,:attribute 必須是唯一的。', From ff59bbdc0780d0ac5e197ce16b99713b36a6ade8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 24 Jan 2026 13:53:55 +0000 Subject: [PATCH 023/204] Updated translator & dependency attribution before release v25.12.2 --- .github/translators.txt | 7 +++++++ dev/licensing/php-library-licenses.txt | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/.github/translators.txt b/.github/translators.txt index b69770939f8..461e89d1666 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -521,3 +521,10 @@ setiawan setiawan (culture.setiawan) :: Indonesian Donald Mac Kenzie (kiuman) :: Norwegian Bokmal Gabriel Silver (GabrielBSilver) :: Hebrew Tomas Darius Davainis (Tomasdd) :: Lithuanian +CriedHero :: Chinese Simplified +Henrik (henrik2105) :: Norwegian Bokmal +FoW (fofwisdom) :: Korean +serinf-lauza :: French +Diyan Nikolaev (nikolaev.diyan) :: Bulgarian +Shadluk Avan (quldosh) :: Uzbek +Marci (MartonPoto) :: Hungarian diff --git a/dev/licensing/php-library-licenses.txt b/dev/licensing/php-library-licenses.txt index d178e92434d..d0cc347d910 100644 --- a/dev/licensing/php-library-licenses.txt +++ b/dev/licensing/php-library-licenses.txt @@ -760,6 +760,13 @@ Copyright: Copyright (c) 2014-present Fabien Potencier Source: https://github.com/symfony/var-dumper.git Link: https://symfony.com ----------- +thecodingmachine/safe +License: MIT +License File: vendor/thecodingmachine/safe/LICENSE +Copyright: Copyright (c) 2018 TheCodingMachine +Source: https://github.com/thecodingmachine/safe.git +Link: https://github.com/thecodingmachine/safe.git +----------- tijsverkoyen/css-to-inline-styles License: BSD-3-Clause License File: vendor/tijsverkoyen/css-to-inline-styles/LICENSE.md From 36649a618858e0309fce0f1d5ce90bfaf47c4605 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 26 Jan 2026 11:55:39 +0000 Subject: [PATCH 024/204] Theme: Updated view registration to be dynamic Within the responsibility of the theme service instead of being part of the app configuration. --- app/App/Providers/ThemeServiceProvider.php | 1 + app/Config/view.php | 8 +------- app/Theming/ThemeService.php | 10 ++++++++++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/App/Providers/ThemeServiceProvider.php b/app/App/Providers/ThemeServiceProvider.php index 2cf581d3863..a806c1df622 100644 --- a/app/App/Providers/ThemeServiceProvider.php +++ b/app/App/Providers/ThemeServiceProvider.php @@ -24,6 +24,7 @@ public function boot(): void { // Boot up the theme system $themeService = $this->app->make(ThemeService::class); + $themeService->registerViewPathsForTheme($this->app->make('view')->getFinder()); $themeService->readThemeActions(); $themeService->dispatch(ThemeEvents::APP_BOOT, $this->app); } diff --git a/app/Config/view.php b/app/Config/view.php index 80bc9ef8fe8..2eb30b4c9de 100644 --- a/app/Config/view.php +++ b/app/Config/view.php @@ -8,12 +8,6 @@ * Do not edit this file unless you're happy to maintain any changes yourself. */ -// Join up possible view locations -$viewPaths = [realpath(base_path('resources/views'))]; -if ($theme = env('APP_THEME', false)) { - array_unshift($viewPaths, base_path('themes/' . $theme)); -} - return [ // App theme @@ -26,7 +20,7 @@ // Most templating systems load templates from disk. Here you may specify // an array of paths that should be checked for your views. Of course // the usual Laravel view path has already been registered for you. - 'paths' => $viewPaths, + 'paths' => [realpath(base_path('resources/views'))], // Compiled View Path // This option determines where all the compiled Blade templates will be diff --git a/app/Theming/ThemeService.php b/app/Theming/ThemeService.php index 4bdb6836b02..87811f0efd1 100644 --- a/app/Theming/ThemeService.php +++ b/app/Theming/ThemeService.php @@ -6,6 +6,7 @@ use BookStack\Exceptions\ThemeException; use Illuminate\Console\Application; use Illuminate\Console\Application as Artisan; +use Illuminate\View\FileViewFinder; use Symfony\Component\Console\Command\Command; class ThemeService @@ -90,6 +91,15 @@ public function readThemeActions(): void } } + /** + * Register any extra paths for where we may expect views to be located + * with the provided FileViewFinder, to make custom views available for use. + */ + public function registerViewPathsForTheme(FileViewFinder $finder): void + { + $finder->prependLocation(theme_path()); + } + /** * @see SocialDriverManager::addSocialDriver */ From c32b1686a95e17ab143faac1eb6b1611fe0e5286 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 26 Jan 2026 17:16:14 +0000 Subject: [PATCH 025/204] Theme: Added the ability to add views before/after existing ones Adds a registration system via the logical theme system, to tell BookStack about views to render before or after a specific template is included in the system. --- app/App/Providers/ThemeServiceProvider.php | 17 ++++- app/Theming/ThemeService.php | 88 ++++++++++++++++++++-- 2 files changed, 96 insertions(+), 9 deletions(-) diff --git a/app/App/Providers/ThemeServiceProvider.php b/app/App/Providers/ThemeServiceProvider.php index a806c1df622..98ad509f355 100644 --- a/app/App/Providers/ThemeServiceProvider.php +++ b/app/App/Providers/ThemeServiceProvider.php @@ -4,7 +4,9 @@ use BookStack\Theming\ThemeEvents; use BookStack\Theming\ThemeService; +use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; +use Illuminate\View\View; class ThemeServiceProvider extends ServiceProvider { @@ -24,8 +26,17 @@ public function boot(): void { // Boot up the theme system $themeService = $this->app->make(ThemeService::class); - $themeService->registerViewPathsForTheme($this->app->make('view')->getFinder()); - $themeService->readThemeActions(); - $themeService->dispatch(ThemeEvents::APP_BOOT, $this->app); + + $viewFactory = $this->app->make('view'); + $themeService->registerViewPathsForTheme($viewFactory->getFinder()); + + if ($themeService->logicalThemeIsActive()) { + $themeService->readThemeActions(); + $themeService->dispatch(ThemeEvents::APP_BOOT, $this->app); + $viewFactory->share('__theme', $themeService); + Blade::directive('include', function ($expression) { + return "handleViewInclude({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>"; + }); + } } } diff --git a/app/Theming/ThemeService.php b/app/Theming/ThemeService.php index 87811f0efd1..9587ceccb3f 100644 --- a/app/Theming/ThemeService.php +++ b/app/Theming/ThemeService.php @@ -16,6 +16,16 @@ class ThemeService */ protected array $listeners = []; + /** + * @var array> + */ + protected array $beforeViews = []; + + /** + * @var array> + */ + protected array $afterViews = []; + /** * Get the currently configured theme. * Returns an empty string if not configured. @@ -82,15 +92,22 @@ public function registerCommand(Command $command): void public function readThemeActions(): void { $themeActionsFile = theme_path('functions.php'); - if ($themeActionsFile && file_exists($themeActionsFile)) { - try { - require $themeActionsFile; - } catch (\Error $exception) { - throw new ThemeException("Failed loading theme functions file at \"{$themeActionsFile}\" with error: {$exception->getMessage()}"); - } + try { + require $themeActionsFile; + } catch (\Error $exception) { + throw new ThemeException("Failed loading theme functions file at \"{$themeActionsFile}\" with error: {$exception->getMessage()}"); } } + /** + * Check if a logical theme is active. + */ + public function logicalThemeIsActive(): bool + { + $themeActionsFile = theme_path('functions.php'); + return $themeActionsFile && file_exists($themeActionsFile); + } + /** * Register any extra paths for where we may expect views to be located * with the provided FileViewFinder, to make custom views available for use. @@ -108,4 +125,63 @@ public function addSocialDriver(string $driverName, array $config, string $socia $driverManager = app()->make(SocialDriverManager::class); $driverManager->addSocialDriver($driverName, $config, $socialiteHandler, $configureForRedirect); } + + /** + * Provide the response for a blade template view include. + */ + public function handleViewInclude(string $viewPath, array $data = []): string + { + $viewsContent = [ + ...$this->renderViewSets($this->beforeViews[$viewPath] ?? [], $data), + view()->make($viewPath, $data)->render(), + ...$this->renderViewSets($this->afterViews[$viewPath] ?? [], $data), + ]; + + return implode("\n", $viewsContent); + } + + /** + * Register a custom view to be rendered before the given target view is included in the template system. + */ + public function registerViewRenderBefore(string $targetView, string $localView, int $priority = 50): void + { + $this->registerAdjacentView($this->beforeViews, $targetView, $localView, $priority); + } + + /** + * Register a custom view to be rendered after the given target view is included in the template system. + */ + public function registerViewRenderAfter(string $targetView, string $localView, int $priority = 50): void + { + $this->registerAdjacentView($this->afterViews, $targetView, $localView, $priority); + } + + protected function registerAdjacentView(array &$location, string $targetView, string $localView, int $priority = 50): void + { + $viewPath = theme_path($localView . '.blade.php'); + if (!file_exists($viewPath)) { + throw new ThemeException("Expected registered view file at \"{$viewPath}\" does not exist"); + } + + if (!isset($location[$targetView])) { + $location[$targetView] = []; + } + $location[$targetView][$viewPath] = $priority; + } + + /** + * @param array $viewSet + * @return string[] + */ + protected function renderViewSets(array $viewSet, array $data): array + { + $paths = array_keys($viewSet); + usort($paths, function (string $a, string $b) use ($viewSet) { + return $viewSet[$a] <=> $viewSet[$b]; + }); + + return array_map(function (string $viewPath) use ($data) { + return view()->file($viewPath, $data)->render(); + }, $paths); + } } From 9fcfc762ec9bf36b173a002fccbc702eeba410b3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 27 Jan 2026 00:36:35 +0000 Subject: [PATCH 026/204] Theme: Added testing of registerViewToRender* functions Updated function name also. --- app/App/helpers.php | 3 +-- app/Theming/ThemeService.php | 4 ++-- tests/ThemeTest.php | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/app/App/helpers.php b/app/App/helpers.php index 0e357e36aee..8f210ecafd4 100644 --- a/app/App/helpers.php +++ b/app/App/helpers.php @@ -81,8 +81,7 @@ function setting(?string $key = null, mixed $default = null): mixed /** * Get a path to a theme resource. - * Returns null if a theme is not configured and - * therefore a full path is not available for use. + * Returns null if a theme is not configured, and therefore a full path is not available for use. */ function theme_path(string $path = ''): ?string { diff --git a/app/Theming/ThemeService.php b/app/Theming/ThemeService.php index 9587ceccb3f..0a6327af85a 100644 --- a/app/Theming/ThemeService.php +++ b/app/Theming/ThemeService.php @@ -143,7 +143,7 @@ public function handleViewInclude(string $viewPath, array $data = []): string /** * Register a custom view to be rendered before the given target view is included in the template system. */ - public function registerViewRenderBefore(string $targetView, string $localView, int $priority = 50): void + public function registerViewToRenderBefore(string $targetView, string $localView, int $priority = 50): void { $this->registerAdjacentView($this->beforeViews, $targetView, $localView, $priority); } @@ -151,7 +151,7 @@ public function registerViewRenderBefore(string $targetView, string $localView, /** * Register a custom view to be rendered after the given target view is included in the template system. */ - public function registerViewRenderAfter(string $targetView, string $localView, int $priority = 50): void + public function registerViewToRenderAfter(string $targetView, string $localView, int $priority = 50): void { $this->registerAdjacentView($this->afterViews, $targetView, $localView, $priority); } diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php index 841ff78caf0..014f3a92f18 100644 --- a/tests/ThemeTest.php +++ b/tests/ThemeTest.php @@ -492,6 +492,38 @@ public function test_public_folder_contents_accessible_via_route() }); } + public function test_register_view_to_render_before_and_after() + { + $this->usingThemeFolder(function (string $folder) { + $before = 'this-is-my-before-header-string'; + $afterA = 'this-is-my-after-header-string-a'; + $afterB = 'this-is-my-after-header-string-b'; + $afterC = 'this-is-my-after-header-string-{{ 1+51 }}'; + + $functionsContent = <<<'CONTENT' +refreshApplication(); + + $resp = $this->get('/login'); + $resp->assertSee($before); + // Ensure ordering of the multiple after views + $resp->assertSee($afterB . "\n" . $afterA . "\nthis-is-my-after-header-string-52"); + }); + } + protected function usingThemeFolder(callable $callback) { // Create a folder and configure a theme From 1b17bb3929d35410bfb5ed80f15bad911f84e832 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 27 Jan 2026 16:50:50 +0000 Subject: [PATCH 027/204] Theme: Changed how before/after views are registered Changed the system out to be a theme event instead of method, to align with other registration events, and so that the theme view work can better be contained in its own class. --- app/App/Providers/ThemeServiceProvider.php | 21 +++-- app/Theming/ThemeEvents.php | 10 +++ app/Theming/ThemeService.php | 91 +------------------- app/Theming/ThemeViews.php | 96 ++++++++++++++++++++++ tests/ThemeTest.php | 17 ++-- 5 files changed, 135 insertions(+), 100 deletions(-) create mode 100644 app/Theming/ThemeViews.php diff --git a/app/App/Providers/ThemeServiceProvider.php b/app/App/Providers/ThemeServiceProvider.php index 98ad509f355..e32f90b9afe 100644 --- a/app/App/Providers/ThemeServiceProvider.php +++ b/app/App/Providers/ThemeServiceProvider.php @@ -4,9 +4,9 @@ use BookStack\Theming\ThemeEvents; use BookStack\Theming\ThemeService; +use BookStack\Theming\ThemeViews; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; -use Illuminate\View\View; class ThemeServiceProvider extends ServiceProvider { @@ -26,16 +26,21 @@ public function boot(): void { // Boot up the theme system $themeService = $this->app->make(ThemeService::class); - $viewFactory = $this->app->make('view'); - $themeService->registerViewPathsForTheme($viewFactory->getFinder()); + if (!$themeService->getTheme()) { + return; + } + + $themeService->readThemeActions(); + $themeService->dispatch(ThemeEvents::APP_BOOT, $this->app); - if ($themeService->logicalThemeIsActive()) { - $themeService->readThemeActions(); - $themeService->dispatch(ThemeEvents::APP_BOOT, $this->app); - $viewFactory->share('__theme', $themeService); + $themeViews = new ThemeViews(); + $themeService->dispatch(ThemeEvents::THEME_REGISTER_VIEWS, $themeViews); + $themeViews->registerViewPathsForTheme($viewFactory->getFinder()); + if ($themeViews->hasRegisteredViews()) { + $viewFactory->share('__themeViews', $themeViews); Blade::directive('include', function ($expression) { - return "handleViewInclude({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>"; + return "handleViewInclude({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>"; }); } } diff --git a/app/Theming/ThemeEvents.php b/app/Theming/ThemeEvents.php index 44630acaeb1..c6266b32b9c 100644 --- a/app/Theming/ThemeEvents.php +++ b/app/Theming/ThemeEvents.php @@ -134,6 +134,16 @@ class ThemeEvents */ const ROUTES_REGISTER_WEB_AUTH = 'routes_register_web_auth'; + + /** + * Theme register views event. + * Called by the theme system when a theme is active, so that custom view templates can be registered + * to be rendered in addition to existing app views. + * + * @param \BookStack\Theming\ThemeViews $themeViews + */ + const THEME_REGISTER_VIEWS = 'theme_register_views'; + /** * Web before middleware action. * Runs before the request is handled but after all other middleware apart from those diff --git a/app/Theming/ThemeService.php b/app/Theming/ThemeService.php index 0a6327af85a..14281adca30 100644 --- a/app/Theming/ThemeService.php +++ b/app/Theming/ThemeService.php @@ -16,16 +16,6 @@ class ThemeService */ protected array $listeners = []; - /** - * @var array> - */ - protected array $beforeViews = []; - - /** - * @var array> - */ - protected array $afterViews = []; - /** * Get the currently configured theme. * Returns an empty string if not configured. @@ -92,6 +82,10 @@ public function registerCommand(Command $command): void public function readThemeActions(): void { $themeActionsFile = theme_path('functions.php'); + if (!$themeActionsFile || !file_exists($themeActionsFile)) { + return; + } + try { require $themeActionsFile; } catch (\Error $exception) { @@ -99,24 +93,6 @@ public function readThemeActions(): void } } - /** - * Check if a logical theme is active. - */ - public function logicalThemeIsActive(): bool - { - $themeActionsFile = theme_path('functions.php'); - return $themeActionsFile && file_exists($themeActionsFile); - } - - /** - * Register any extra paths for where we may expect views to be located - * with the provided FileViewFinder, to make custom views available for use. - */ - public function registerViewPathsForTheme(FileViewFinder $finder): void - { - $finder->prependLocation(theme_path()); - } - /** * @see SocialDriverManager::addSocialDriver */ @@ -125,63 +101,4 @@ public function addSocialDriver(string $driverName, array $config, string $socia $driverManager = app()->make(SocialDriverManager::class); $driverManager->addSocialDriver($driverName, $config, $socialiteHandler, $configureForRedirect); } - - /** - * Provide the response for a blade template view include. - */ - public function handleViewInclude(string $viewPath, array $data = []): string - { - $viewsContent = [ - ...$this->renderViewSets($this->beforeViews[$viewPath] ?? [], $data), - view()->make($viewPath, $data)->render(), - ...$this->renderViewSets($this->afterViews[$viewPath] ?? [], $data), - ]; - - return implode("\n", $viewsContent); - } - - /** - * Register a custom view to be rendered before the given target view is included in the template system. - */ - public function registerViewToRenderBefore(string $targetView, string $localView, int $priority = 50): void - { - $this->registerAdjacentView($this->beforeViews, $targetView, $localView, $priority); - } - - /** - * Register a custom view to be rendered after the given target view is included in the template system. - */ - public function registerViewToRenderAfter(string $targetView, string $localView, int $priority = 50): void - { - $this->registerAdjacentView($this->afterViews, $targetView, $localView, $priority); - } - - protected function registerAdjacentView(array &$location, string $targetView, string $localView, int $priority = 50): void - { - $viewPath = theme_path($localView . '.blade.php'); - if (!file_exists($viewPath)) { - throw new ThemeException("Expected registered view file at \"{$viewPath}\" does not exist"); - } - - if (!isset($location[$targetView])) { - $location[$targetView] = []; - } - $location[$targetView][$viewPath] = $priority; - } - - /** - * @param array $viewSet - * @return string[] - */ - protected function renderViewSets(array $viewSet, array $data): array - { - $paths = array_keys($viewSet); - usort($paths, function (string $a, string $b) use ($viewSet) { - return $viewSet[$a] <=> $viewSet[$b]; - }); - - return array_map(function (string $viewPath) use ($data) { - return view()->file($viewPath, $data)->render(); - }, $paths); - } } diff --git a/app/Theming/ThemeViews.php b/app/Theming/ThemeViews.php new file mode 100644 index 00000000000..719f8e3ce24 --- /dev/null +++ b/app/Theming/ThemeViews.php @@ -0,0 +1,96 @@ +> + */ + protected array $beforeViews = []; + + /** + * @var array> + */ + protected array $afterViews = []; + + /** + * Register any extra paths for where we may expect views to be located + * with the provided FileViewFinder, to make custom views available for use. + */ + public function registerViewPathsForTheme(FileViewFinder $finder): void + { + $finder->prependLocation(theme_path()); + } + + /** + * Provide the response for a blade template view include. + */ + public function handleViewInclude(string $viewPath, array $data = []): string + { + if (!$this->hasRegisteredViews()) { + return view()->make($viewPath, $data)->render(); + } + + $viewsContent = [ + ...$this->renderViewSets($this->beforeViews[$viewPath] ?? [], $data), + view()->make($viewPath, $data)->render(), + ...$this->renderViewSets($this->afterViews[$viewPath] ?? [], $data), + ]; + + return implode("\n", $viewsContent); + } + + /** + * Register a custom view to be rendered before the given target view is included in the template system. + */ + public function renderBefore(string $targetView, string $localView, int $priority = 50): void + { + $this->registerAdjacentView($this->beforeViews, $targetView, $localView, $priority); + } + + /** + * Register a custom view to be rendered after the given target view is included in the template system. + */ + public function renderAfter(string $targetView, string $localView, int $priority = 50): void + { + $this->registerAdjacentView($this->afterViews, $targetView, $localView, $priority); + } + + public function hasRegisteredViews(): bool + { + return !empty($this->beforeViews) && !empty($this->afterViews); + } + + protected function registerAdjacentView(array &$location, string $targetView, string $localView, int $priority = 50): void + { + $viewPath = theme_path($localView . '.blade.php'); + if (!file_exists($viewPath)) { + throw new ThemeException("Expected registered view file at \"{$viewPath}\" does not exist"); + } + + if (!isset($location[$targetView])) { + $location[$targetView] = []; + } + $location[$targetView][$viewPath] = $priority; + } + + /** + * @param array $viewSet + * @return string[] + */ + protected function renderViewSets(array $viewSet, array $data): array + { + $paths = array_keys($viewSet); + usort($paths, function (string $a, string $b) use ($viewSet) { + return $viewSet[$a] <=> $viewSet[$b]; + }); + + return array_map(function (string $viewPath) use ($data) { + return view()->file($viewPath, $data)->render(); + }, $paths); + } +} diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php index 014f3a92f18..f640513cf1d 100644 --- a/tests/ThemeTest.php +++ b/tests/ThemeTest.php @@ -492,7 +492,7 @@ public function test_public_folder_contents_accessible_via_route() }); } - public function test_register_view_to_render_before_and_after() + public function test_theme_register_views_event_to_insert_views_before_and_after() { $this->usingThemeFolder(function (string $folder) { $before = 'this-is-my-before-header-string'; @@ -502,10 +502,14 @@ public function test_register_view_to_render_before_and_after() $functionsContent = <<<'CONTENT' renderBefore('layouts.parts.header', 'before', 4); + $themeViews->renderAfter('layouts.parts.header', 'after-a', 4); + $themeViews->renderAfter('layouts.parts.header', 'after-b', 1); + $themeViews->renderAfter('layouts.parts.header', 'after-c', 12); +}); CONTENT; $viewDir = theme_path(); @@ -516,12 +520,15 @@ public function test_register_view_to_render_before_and_after() file_put_contents($viewDir . '/after-c.blade.php', $afterC); $this->refreshApplication(); + $this->artisan('view:clear'); $resp = $this->get('/login'); $resp->assertSee($before); // Ensure ordering of the multiple after views $resp->assertSee($afterB . "\n" . $afterA . "\nthis-is-my-after-header-string-52"); }); + + $this->artisan('view:clear'); } protected function usingThemeFolder(callable $callback) From 6a63b38bb305eb5d110230b31f1ca525da81ef47 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 29 Jan 2026 03:37:16 +0000 Subject: [PATCH 028/204] API: Prevented non-GET requests when using cookie-based auth Added test to cover. --- app/Http/Middleware/ApiAuthenticate.php | 12 +++++++++--- lang/en/errors.php | 1 + tests/Api/ApiAuthTest.php | 21 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/app/Http/Middleware/ApiAuthenticate.php b/app/Http/Middleware/ApiAuthenticate.php index 15b5a325a10..4123385c0ea 100644 --- a/app/Http/Middleware/ApiAuthenticate.php +++ b/app/Http/Middleware/ApiAuthenticate.php @@ -17,7 +17,7 @@ class ApiAuthenticate public function handle(Request $request, Closure $next) { // Validate the token and it's users API access - $this->ensureAuthorizedBySessionOrToken(); + $this->ensureAuthorizedBySessionOrToken($request); return $next($request); } @@ -28,15 +28,21 @@ public function handle(Request $request, Closure $next) * * @throws ApiAuthException */ - protected function ensureAuthorizedBySessionOrToken(): void + protected function ensureAuthorizedBySessionOrToken(Request $request): void { // Return if the user is already found to be signed in via session-based auth. - // This is to make it easy to browser the API via browser after just logging into the system. + // This is to make it easy to browse the API via browser when exploring endpoints via the UI. if (!user()->isGuest() || session()->isStarted()) { + // Ensure the user has API access permission if (!$this->sessionUserHasApiAccess()) { throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403); } + // Only allow GET requests for cookie-based API usage + if ($request->method() !== 'GET') { + throw new ApiAuthException(trans('errors.api_cookie_auth_only_get'), 403); + } + return; } diff --git a/lang/en/errors.php b/lang/en/errors.php index 77d7ee69e49..20537d59f0c 100644 --- a/lang/en/errors.php +++ b/lang/en/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'The secret provided for the given used API token is incorrect', 'api_user_no_api_permission' => 'The owner of the used API token does not have permission to make API calls', 'api_user_token_expired' => 'The authorization token used has expired', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', diff --git a/tests/Api/ApiAuthTest.php b/tests/Api/ApiAuthTest.php index 4e446bf5d1a..c8472ce7f8e 100644 --- a/tests/Api/ApiAuthTest.php +++ b/tests/Api/ApiAuthTest.php @@ -112,6 +112,27 @@ public function test_access_prevented_for_guest_users_with_api_permission_while_ $resp->assertStatus(200); } + public function test_only_get_requests_are_supported_with_session_auth() + { + $user = $this->users->admin(); + $this->actingAs($user, 'standard'); + + $uriByMethods = [ + 'POST' => '/books', + 'PUT' => '/books/1', + 'DELETE' => '/books/1', + 'HEAD' => '/books', + ]; + + foreach ($uriByMethods as $method => $uri) { + $resp = $this->withCredentials()->json($method, "/api{$uri}"); + $resp->assertStatus(403); + if ($method !== 'HEAD') { + $resp->assertJson($this->errorResponse('Only GET requests are allowed when using the API with cookie-based authentication', 403)); + } + } + } + public function test_token_expiry_checked() { $editor = $this->users->editor(); From c77a0fdff361b816db59f96315978bf1aa636b17 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 29 Jan 2026 14:54:08 +0000 Subject: [PATCH 029/204] Page Content: Added form elements to filtering Added and updated tests to cover. Also updated API auth to a narrower focus of existing session instead of also existing user auth. This is mainly for tests, to ensure they're following the session process we'd see for activity in the UI. --- app/Activity/Models/Comment.php | 2 +- app/Entities/Tools/EntityHtmlDescription.php | 2 +- app/Entities/Tools/PageContent.php | 2 +- app/Http/Middleware/ApiAuthenticate.php | 8 +- app/Theming/CustomHtmlHeadContentProvider.php | 2 +- app/Util/HtmlContentFilter.php | 62 ++++++++-- tests/Api/ApiAuthTest.php | 5 +- tests/Api/ChaptersApiTest.php | 2 +- tests/Api/RecycleBinApiTest.php | 10 +- tests/Api/UsersApiTest.php | 2 +- tests/Entity/PageContentTest.php | 110 ++++++++++++++++-- 11 files changed, 176 insertions(+), 31 deletions(-) diff --git a/app/Activity/Models/Comment.php b/app/Activity/Models/Comment.php index 0f8e5d785a1..ce05e3df35b 100644 --- a/app/Activity/Models/Comment.php +++ b/app/Activity/Models/Comment.php @@ -82,7 +82,7 @@ public function logDescriptor(): string public function safeHtml(): string { - return HtmlContentFilter::removeScriptsFromHtmlString($this->html ?? ''); + return HtmlContentFilter::removeActiveContentFromHtmlString($this->html ?? ''); } public function jointPermissions(): HasMany diff --git a/app/Entities/Tools/EntityHtmlDescription.php b/app/Entities/Tools/EntityHtmlDescription.php index 335703c36ad..b14deb257a7 100644 --- a/app/Entities/Tools/EntityHtmlDescription.php +++ b/app/Entities/Tools/EntityHtmlDescription.php @@ -50,7 +50,7 @@ public function getHtml(bool $raw = false): string return $html; } - return HtmlContentFilter::removeScriptsFromHtmlString($html); + return HtmlContentFilter::removeActiveContentFromHtmlString($html); } public function getPlain(): string diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index c7a59216ad0..5358e8f0c5b 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -318,7 +318,7 @@ public function render(bool $blankIncludes = false): string } if (!config('app.allow_content_scripts')) { - HtmlContentFilter::removeScriptsFromDocument($doc); + HtmlContentFilter::removeActiveContentFromDocument($doc); } return $doc->getBodyInnerHtml(); diff --git a/app/Http/Middleware/ApiAuthenticate.php b/app/Http/Middleware/ApiAuthenticate.php index 4123385c0ea..bfee87f6c65 100644 --- a/app/Http/Middleware/ApiAuthenticate.php +++ b/app/Http/Middleware/ApiAuthenticate.php @@ -30,9 +30,9 @@ public function handle(Request $request, Closure $next) */ protected function ensureAuthorizedBySessionOrToken(Request $request): void { - // Return if the user is already found to be signed in via session-based auth. - // This is to make it easy to browse the API via browser when exploring endpoints via the UI. - if (!user()->isGuest() || session()->isStarted()) { + // Use the active user session already exists. + // This is to make it easy to explore API endpoints via the UI. + if (session()->isStarted()) { // Ensure the user has API access permission if (!$this->sessionUserHasApiAccess()) { throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403); @@ -49,7 +49,7 @@ protected function ensureAuthorizedBySessionOrToken(Request $request): void // Set our api guard to be the default for this request lifecycle. auth()->shouldUse('api'); - // Validate the token and it's users API access + // Validate the token and its users API access auth()->authenticate(); } diff --git a/app/Theming/CustomHtmlHeadContentProvider.php b/app/Theming/CustomHtmlHeadContentProvider.php index 95d9ff5ad74..e0cf5b3b5c7 100644 --- a/app/Theming/CustomHtmlHeadContentProvider.php +++ b/app/Theming/CustomHtmlHeadContentProvider.php @@ -50,7 +50,7 @@ public function forExport(): string $hash = md5($content); return $this->cache->remember('custom-head-export:' . $hash, 86400, function () use ($content) { - return HtmlContentFilter::removeScriptsFromHtmlString($content); + return HtmlContentFilter::removeActiveContentFromHtmlString($content); }); } diff --git a/app/Util/HtmlContentFilter.php b/app/Util/HtmlContentFilter.php index 75859172977..ad5bf8c5fd3 100644 --- a/app/Util/HtmlContentFilter.php +++ b/app/Util/HtmlContentFilter.php @@ -9,9 +9,11 @@ class HtmlContentFilter { /** - * Remove all the script elements from the given HTML document. + * Remove all active content from the given HTML document. + * This aims to cover anything which can dynamically deal with, or send, data + * like any JavaScript actions or form content. */ - public static function removeScriptsFromDocument(HtmlDocument $doc) + public static function removeActiveContentFromDocument(HtmlDocument $doc): void { // Remove standard script tags $scriptElems = $doc->queryXPath('//script'); @@ -21,7 +23,7 @@ public static function removeScriptsFromDocument(HtmlDocument $doc) $badLinks = $doc->queryXPath('//*[' . static::xpathContains('@href', 'javascript:') . ']'); static::removeNodes($badLinks); - // Remove forms with calls to JavaScript URI + // Remove elements with form-like attributes with calls to JavaScript URI $badForms = $doc->queryXPath('//*[' . static::xpathContains('@action', 'javascript:') . '] | //*[' . static::xpathContains('@formaction', 'javascript:') . ']'); static::removeNodes($badForms); @@ -47,25 +49,71 @@ public static function removeScriptsFromDocument(HtmlDocument $doc) // Remove 'on*' attributes $onAttributes = $doc->queryXPath('//@*[starts-with(name(), \'on\')]'); static::removeAttributes($onAttributes); + + // Remove form elements + $formElements = ['form', 'fieldset', 'button', 'textarea', 'select']; + foreach ($formElements as $formElement) { + $matchingFormElements = $doc->queryXPath('//' . $formElement); + static::removeNodes($matchingFormElements); + } + + // Remove non-checkbox inputs + $inputsToRemove = $doc->queryXPath('//input'); + /** @var DOMElement $input */ + foreach ($inputsToRemove as $input) { + $type = strtolower($input->getAttribute('type')); + if ($type !== 'checkbox') { + $input->parentNode->removeChild($input); + } + } + + // Remove form attributes + $formAttrs = ['form', 'formaction', 'formmethod', 'formtarget']; + foreach ($formAttrs as $formAttr) { + $matchingFormAttrs = $doc->queryXPath('//@' . $formAttr); + static::removeAttributes($matchingFormAttrs); + } } /** - * Remove scripts from the given HTML string. + * Remove active content from the given HTML string. + * This aims to cover anything which can dynamically deal with, or send, data + * like any JavaScript actions or form content. */ - public static function removeScriptsFromHtmlString(string $html): string + public static function removeActiveContentFromHtmlString(string $html): string { if (empty($html)) { return $html; } $doc = new HtmlDocument($html); - static::removeScriptsFromDocument($doc); + static::removeActiveContentFromDocument($doc); return $doc->getBodyInnerHtml(); } /** - * Create a xpath contains statement with a translation automatically built within + * Alias using the old method name to avoid potential compatibility breaks during patch release. + * To remove in future feature release. + * @deprecated Use removeActiveContentFromDocument instead. + */ + public static function removeScriptsFromDocument(HtmlDocument $doc): void + { + static::removeActiveContentFromDocument($doc); + } + + /** + * Alias using the old method name to avoid potential compatibility breaks during patch release. + * To remove in future feature release. + * @deprecated Use removeActiveContentFromHtmlString instead. + */ + public static function removeScriptsFromHtmlString(string $html): string + { + return static::removeActiveContentFromHtmlString($html); + } + + /** + * Create an x-path 'contains' statement with a translation automatically built within * to affectively search in a cases-insensitive manner. */ protected static function xpathContains(string $property, string $value): string diff --git a/tests/Api/ApiAuthTest.php b/tests/Api/ApiAuthTest.php index c8472ce7f8e..76c2c9ce932 100644 --- a/tests/Api/ApiAuthTest.php +++ b/tests/Api/ApiAuthTest.php @@ -24,7 +24,8 @@ public function test_requests_succeed_with_default_auth() $this->actingAs($viewer, 'standard'); - $resp = $this->get($this->endpoint); + $this->startSession(); + $resp = $this->withCredentials()->get($this->endpoint); $resp->assertStatus(200); } @@ -75,6 +76,7 @@ public function test_api_access_permission_required_to_access_api_with_session_a { $editor = $this->users->editor(); $this->actingAs($editor, 'standard'); + $this->startSession(); $resp = $this->get($this->endpoint); $resp->assertStatus(200); @@ -116,6 +118,7 @@ public function test_only_get_requests_are_supported_with_session_auth() { $user = $this->users->admin(); $this->actingAs($user, 'standard'); + $this->startSession(); $uriByMethods = [ 'POST' => '/books', diff --git a/tests/Api/ChaptersApiTest.php b/tests/Api/ChaptersApiTest.php index 194140a569a..953b3a0f5b4 100644 --- a/tests/Api/ChaptersApiTest.php +++ b/tests/Api/ChaptersApiTest.php @@ -252,7 +252,7 @@ public function test_update_with_new_book_id_requires_delete_permission() { $editor = $this->users->editor(); $this->permissions->removeUserRolePermissions($editor, ['chapter-delete-all', 'chapter-delete-own']); - $this->actingAs($editor); + $this->actingAsForApi($editor); $chapter = $this->entities->chapterHasPages(); $newBook = Book::query()->where('id', '!=', $chapter->book_id)->first(); diff --git a/tests/Api/RecycleBinApiTest.php b/tests/Api/RecycleBinApiTest.php index 6ccc69c3545..9e645fe215b 100644 --- a/tests/Api/RecycleBinApiTest.php +++ b/tests/Api/RecycleBinApiTest.php @@ -23,7 +23,7 @@ public function test_settings_manage_permission_needed_for_all_endpoints() { $editor = $this->users->editor(); $this->permissions->grantUserRolePermissions($editor, ['settings-manage']); - $this->actingAs($editor); + $this->actingAsForApi($editor); foreach ($this->endpointMap as [$method, $uri]) { $resp = $this->json($method, $uri); @@ -36,7 +36,7 @@ public function test_restrictions_manage_all_permission_needed_for_all_endpoints { $editor = $this->users->editor(); $this->permissions->grantUserRolePermissions($editor, ['restrictions-manage-all']); - $this->actingAs($editor); + $this->actingAsForApi($editor); foreach ($this->endpointMap as [$method, $uri]) { $resp = $this->json($method, $uri); @@ -53,6 +53,7 @@ public function test_index_endpoint_returns_expected_page() $book = $this->entities->book(); $this->actingAs($admin)->delete($page->getUrl()); $this->delete($book->getUrl()); + $this->actingAsForApi($admin); $deletions = Deletion::query()->orderBy('id')->get(); @@ -89,7 +90,7 @@ public function test_index_endpoint_returns_children_count() $deletion = Deletion::query()->orderBy('id')->first(); - $resp = $this->getJson($this->baseEndpoint); + $resp = $this->actingAsForApi($admin)->getJson($this->baseEndpoint); $expectedData = [ [ @@ -115,6 +116,7 @@ public function test_index_endpoint_returns_parent() $this->actingAs($admin)->delete($page->getUrl()); $deletion = Deletion::query()->orderBy('id')->first(); + $this->actingAsForApi($admin); $resp = $this->getJson($this->baseEndpoint); $expectedData = [ @@ -141,6 +143,7 @@ public function test_restore_endpoint() $page = $this->entities->page(); $this->asAdmin()->delete($page->getUrl()); $page->refresh(); + $this->actingAsApiAdmin(); $deletion = Deletion::query()->orderBy('id')->first(); @@ -165,6 +168,7 @@ public function test_destroy_endpoint() $page = $this->entities->page(); $this->asAdmin()->delete($page->getUrl()); $page->refresh(); + $this->actingAsApiAdmin(); $deletion = Deletion::query()->orderBy('id')->first(); diff --git a/tests/Api/UsersApiTest.php b/tests/Api/UsersApiTest.php index a0c67d0d281..e7b9df6aae9 100644 --- a/tests/Api/UsersApiTest.php +++ b/tests/Api/UsersApiTest.php @@ -80,7 +80,7 @@ public function test_index_endpoint_has_correct_created_and_last_activity_dates( /** @var ActivityModel $activity */ $activity = ActivityModel::query()->where('user_id', '=', $user->id)->latest()->first(); - $resp = $this->asAdmin()->getJson($this->baseEndpoint . '?filter[id]=3'); + $resp = $this->actingAsApiAdmin()->getJson($this->baseEndpoint . '?filter[id]=3'); $resp->assertJson(['data' => [ [ 'id' => $user->id, diff --git a/tests/Entity/PageContentTest.php b/tests/Entity/PageContentTest.php index 23a38b5735b..77026113012 100644 --- a/tests/Entity/PageContentTest.php +++ b/tests/Entity/PageContentTest.php @@ -208,11 +208,11 @@ public function test_javascript_uri_links_are_removed() public function test_form_actions_with_javascript_are_removed() { $checks = [ - '
    ', - '
    ', - '
    ', - '
    ', - '
    ', + '', + 'Click me', + 'Click me', + '', + '', ]; $this->asEditor(); @@ -224,11 +224,101 @@ public function test_form_actions_with_javascript_are_removed() $pageView = $this->get($page->getUrl()); $pageView->assertStatus(200); - $this->withHtml($pageView)->assertElementNotContains('.page-content', '', + '

    thisisacattofind

    ', + <<<'TESTCASE' + + + + +

    thisisacattofind

    +
    +

    thisdogshouldnotbefound

    +
    + + + + +
    +
    +TESTCASE + + ]; + + $this->asEditor(); + $page = $this->entities->page(); + + foreach ($checks as $check) { + $page->html = $check; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $pageView->assertSee('thisisacattofind'); + $pageView->assertDontSee('thisdogshouldnotbefound'); + } + } + + public function test_form_attributes_are_removed() + { + $withinSvgSample = <<<'TESTCASE' + + + + +

    thisisacattofind

    +

    thisisacattofind

    + + +
    +
    +TESTCASE; + + $checks = [ + 'formaction' => '

    thisisacattofind

    ', + 'form' => '

    thisisacattofind

    ', + 'formmethod' => '

    thisisacattofind

    ', + 'formtarget' => '

    thisisacattofind

    ', + 'FORMTARGET' => '

    thisisacattofind

    ', + ]; + + $this->asEditor(); + $page = $this->entities->page(); + + foreach ($checks as $attribute => $check) { + $page->html = $check; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $pageView->assertSee('thisisacattofind'); + $this->withHtml($pageView)->assertElementNotExists(".page-content [{$attribute}]"); + } + + $page->html = $withinSvgSample; + $page->save(); + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $html = $this->withHtml($pageView); + foreach ($checks as $attribute => $check) { + $pageView->assertSee('thisisacattofind'); + $html->assertElementNotExists(".page-content [{$attribute}]"); } } From 3e5e88dc8783123a6d828e76a6d8ff4a6e7e55df Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 29 Jan 2026 14:57:05 +0000 Subject: [PATCH 030/204] Deps: Updated PHP package versions via composer --- composer.lock | 318 +++++++++++++++++++++++++------------------------- 1 file changed, 159 insertions(+), 159 deletions(-) diff --git a/composer.lock b/composer.lock index 7a57dcab392..4db601588b1 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.369.17", + "version": "3.369.22", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "8bdccd2f8e54c5cd170b22f52414171e19226fd1" + "reference": "fe83cbc3adb5ed384179ac6d63531aadde0198e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/8bdccd2f8e54c5cd170b22f52414171e19226fd1", - "reference": "8bdccd2f8e54c5cd170b22f52414171e19226fd1", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/fe83cbc3adb5ed384179ac6d63531aadde0198e3", + "reference": "fe83cbc3adb5ed384179ac6d63531aadde0198e3", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.369.17" + "source": "https://github.com/aws/aws-sdk-php/tree/3.369.22" }, - "time": "2026-01-21T19:09:32+00:00" + "time": "2026-01-28T19:19:00+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1739,16 +1739,16 @@ }, { "name": "laravel/framework", - "version": "v12.48.1", + "version": "v12.49.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "0f0974a9769378ccd9c9935c09b9927f3a606830" + "reference": "4bde4530545111d8bdd1de6f545fa8824039fcb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/0f0974a9769378ccd9c9935c09b9927f3a606830", - "reference": "0f0974a9769378ccd9c9935c09b9927f3a606830", + "url": "https://api.github.com/repos/laravel/framework/zipball/4bde4530545111d8bdd1de6f545fa8824039fcb5", + "reference": "4bde4530545111d8bdd1de6f545fa8824039fcb5", "shasum": "" }, "require": { @@ -1957,20 +1957,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-01-20T16:12:36+00:00" + "time": "2026-01-28T03:40:49+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.10", + "version": "v0.3.11", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "360ba095ef9f51017473505191fbd4ab73e1cab3" + "reference": "dd2a2ed95acacbcccd32fd98dee4c946ae7a7217" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/360ba095ef9f51017473505191fbd4ab73e1cab3", - "reference": "360ba095ef9f51017473505191fbd4ab73e1cab3", + "url": "https://api.github.com/repos/laravel/prompts/zipball/dd2a2ed95acacbcccd32fd98dee4c946ae7a7217", + "reference": "dd2a2ed95acacbcccd32fd98dee4c946ae7a7217", "shasum": "" }, "require": { @@ -2014,9 +2014,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.10" + "source": "https://github.com/laravel/prompts/tree/v0.3.11" }, - "time": "2026-01-13T20:29:29+00:00" + "time": "2026-01-27T02:55:06+00:00" }, { "name": "laravel/serializable-closure", @@ -2408,16 +2408,16 @@ }, { "name": "league/flysystem", - "version": "3.30.2", + "version": "3.31.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277" + "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", - "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1717e0b3642b0df65ecb0cc89cdd99fa840672ff", + "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff", "shasum": "" }, "require": { @@ -2485,22 +2485,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.30.2" + "source": "https://github.com/thephpleague/flysystem/tree/3.31.0" }, - "time": "2025-11-10T17:13:11+00:00" + "time": "2026-01-23T15:38:47+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.30.1", + "version": "3.31.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "d286e896083bed3190574b8b088b557b59eb66f5" + "reference": "e36a2bc60b06332c92e4435047797ded352b446f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/d286e896083bed3190574b8b088b557b59eb66f5", - "reference": "d286e896083bed3190574b8b088b557b59eb66f5", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/e36a2bc60b06332c92e4435047797ded352b446f", + "reference": "e36a2bc60b06332c92e4435047797ded352b446f", "shasum": "" }, "require": { @@ -2540,22 +2540,22 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.30.1" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.31.0" }, - "time": "2025-10-20T15:27:33+00:00" + "time": "2026-01-23T15:30:45+00:00" }, { "name": "league/flysystem-local", - "version": "3.30.2", + "version": "3.31.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d" + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ab4f9d0d672f601b102936aa728801dd1a11968d", - "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", "shasum": "" }, "require": { @@ -2589,9 +2589,9 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.2" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" }, - "time": "2025-11-10T11:23:37+00:00" + "time": "2026-01-23T15:30:45+00:00" }, { "name": "league/html-to-markdown", @@ -3299,16 +3299,16 @@ }, { "name": "nesbot/carbon", - "version": "3.11.0", + "version": "3.11.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "bdb375400dcd162624531666db4799b36b64e4a1" + "reference": "f438fcc98f92babee98381d399c65336f3a3827f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/bdb375400dcd162624531666db4799b36b64e4a1", - "reference": "bdb375400dcd162624531666db4799b36b64e4a1", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f", + "reference": "f438fcc98f92babee98381d399c65336f3a3827f", "shasum": "" }, "require": { @@ -3332,7 +3332,7 @@ "phpstan/extension-installer": "^1.4.3", "phpstan/phpstan": "^2.1.22", "phpunit/phpunit": "^10.5.53", - "squizlabs/php_codesniffer": "^3.13.4" + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" }, "bin": [ "bin/carbon" @@ -3375,14 +3375,14 @@ } ], "description": "An API extension for DateTime that supports 281 different languages.", - "homepage": "https://carbon.nesbot.com", + "homepage": "https://carbonphp.github.io/carbon/", "keywords": [ "date", "datetime", "time" ], "support": { - "docs": "https://carbon.nesbot.com/docs", + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", "issues": "https://github.com/CarbonPHP/carbon/issues", "source": "https://github.com/CarbonPHP/carbon" }, @@ -3400,7 +3400,7 @@ "type": "tidelift" } ], - "time": "2025-12-02T21:04:28+00:00" + "time": "2026-01-29T09:26:29+00:00" }, { "name": "nette/schema", @@ -3961,16 +3961,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.48", + "version": "3.0.49", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "64065a5679c50acb886e82c07aa139b0f757bb89" + "reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/64065a5679c50acb886e82c07aa139b0f757bb89", - "reference": "64065a5679c50acb886e82c07aa139b0f757bb89", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/6233a1e12584754e6b5daa69fe1289b47775c1b9", + "reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9", "shasum": "" }, "require": { @@ -4051,7 +4051,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.48" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.49" }, "funding": [ { @@ -4067,7 +4067,7 @@ "type": "tidelift" } ], - "time": "2025-12-15T11:51:42+00:00" + "time": "2026-01-27T09:17:28+00:00" }, { "name": "pragmarx/google2fa", @@ -5437,16 +5437,16 @@ }, { "name": "symfony/console", - "version": "v7.4.3", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6" + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/732a9ca6cd9dfd940c639062d5edbde2f6727fb6", - "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6", + "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", "shasum": "" }, "require": { @@ -5511,7 +5511,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.3" + "source": "https://github.com/symfony/console/tree/v7.4.4" }, "funding": [ { @@ -5531,7 +5531,7 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:50:43+00:00" + "time": "2026-01-13T11:36:38+00:00" }, { "name": "symfony/css-selector", @@ -5671,16 +5671,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2" + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/48be2b0653594eea32dcef130cca1c811dcf25c2", - "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8", "shasum": "" }, "require": { @@ -5729,7 +5729,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.4.0" + "source": "https://github.com/symfony/error-handler/tree/v7.4.4" }, "funding": [ { @@ -5749,20 +5749,20 @@ "type": "tidelift" } ], - "time": "2025-11-05T14:29:59+00:00" + "time": "2026-01-20T16:42:42+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d" + "reference": "dc2c0eba1af673e736bb851d747d266108aea746" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9dddcddff1ef974ad87b3708e4b442dc38b2261d", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746", + "reference": "dc2c0eba1af673e736bb851d747d266108aea746", "shasum": "" }, "require": { @@ -5814,7 +5814,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.4" }, "funding": [ { @@ -5834,7 +5834,7 @@ "type": "tidelift" } ], - "time": "2025-10-28T09:38:46+00:00" + "time": "2026-01-05T11:45:34+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -5984,16 +5984,16 @@ }, { "name": "symfony/finder", - "version": "v7.4.3", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "fffe05569336549b20a1be64250b40516d6e8d06" + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/fffe05569336549b20a1be64250b40516d6e8d06", - "reference": "fffe05569336549b20a1be64250b40516d6e8d06", + "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", "shasum": "" }, "require": { @@ -6028,7 +6028,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.3" + "source": "https://github.com/symfony/finder/tree/v7.4.5" }, "funding": [ { @@ -6048,20 +6048,20 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:50:43+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.3", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52" + "reference": "446d0db2b1f21575f1284b74533e425096abdfb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a70c745d4cea48dbd609f4075e5f5cbce453bd52", - "reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/446d0db2b1f21575f1284b74533e425096abdfb6", + "reference": "446d0db2b1f21575f1284b74533e425096abdfb6", "shasum": "" }, "require": { @@ -6110,7 +6110,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.3" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.5" }, "funding": [ { @@ -6130,20 +6130,20 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:23:49+00:00" + "time": "2026-01-27T16:16:02+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.3", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "885211d4bed3f857b8c964011923528a55702aa5" + "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/885211d4bed3f857b8c964011923528a55702aa5", - "reference": "885211d4bed3f857b8c964011923528a55702aa5", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/229eda477017f92bd2ce7615d06222ec0c19e82a", + "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a", "shasum": "" }, "require": { @@ -6229,7 +6229,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.3" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.5" }, "funding": [ { @@ -6249,20 +6249,20 @@ "type": "tidelift" } ], - "time": "2025-12-31T08:43:57+00:00" + "time": "2026-01-28T10:33:42+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.3", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "e472d35e230108231ccb7f51eb6b2100cac02ee4" + "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/e472d35e230108231ccb7f51eb6b2100cac02ee4", - "reference": "e472d35e230108231ccb7f51eb6b2100cac02ee4", + "url": "https://api.github.com/repos/symfony/mailer/zipball/7b750074c40c694ceb34cb926d6dffee231c5cd6", + "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6", "shasum": "" }, "require": { @@ -6313,7 +6313,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.3" + "source": "https://github.com/symfony/mailer/tree/v7.4.4" }, "funding": [ { @@ -6333,20 +6333,20 @@ "type": "tidelift" } ], - "time": "2025-12-16T08:02:06+00:00" + "time": "2026-01-08T08:25:11+00:00" }, { "name": "symfony/mime", - "version": "v7.4.0", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a" + "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/bdb02729471be5d047a3ac4a69068748f1a6be7a", - "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a", + "url": "https://api.github.com/repos/symfony/mime/zipball/b18c7e6e9eee1e19958138df10412f3c4c316148", + "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148", "shasum": "" }, "require": { @@ -6357,15 +6357,15 @@ }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", + "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/type-resolver": "<1.5.1", "symfony/mailer": "<6.4", "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "phpdocumentor/reflection-docblock": "^5.2", "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/property-access": "^6.4|^7.0|^8.0", @@ -6402,7 +6402,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.0" + "source": "https://github.com/symfony/mime/tree/v7.4.5" }, "funding": [ { @@ -6422,7 +6422,7 @@ "type": "tidelift" } ], - "time": "2025-11-16T10:14:42+00:00" + "time": "2026-01-27T08:59:58+00:00" }, { "name": "symfony/polyfill-ctype", @@ -7255,16 +7255,16 @@ }, { "name": "symfony/process", - "version": "v7.4.3", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f" + "reference": "608476f4604102976d687c483ac63a79ba18cc97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/2f8e1a6cdf590ca63715da4d3a7a3327404a523f", - "reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f", + "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", + "reference": "608476f4604102976d687c483ac63a79ba18cc97", "shasum": "" }, "require": { @@ -7296,7 +7296,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.3" + "source": "https://github.com/symfony/process/tree/v7.4.5" }, "funding": [ { @@ -7316,20 +7316,20 @@ "type": "tidelift" } ], - "time": "2025-12-19T10:00:43+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { "name": "symfony/routing", - "version": "v7.4.3", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090" + "reference": "0798827fe2c79caeed41d70b680c2c3507d10147" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090", - "reference": "5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090", + "url": "https://api.github.com/repos/symfony/routing/zipball/0798827fe2c79caeed41d70b680c2c3507d10147", + "reference": "0798827fe2c79caeed41d70b680c2c3507d10147", "shasum": "" }, "require": { @@ -7381,7 +7381,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.3" + "source": "https://github.com/symfony/routing/tree/v7.4.4" }, "funding": [ { @@ -7401,7 +7401,7 @@ "type": "tidelift" } ], - "time": "2025-12-19T10:00:43+00:00" + "time": "2026-01-12T12:19:02+00:00" }, { "name": "symfony/service-contracts", @@ -7492,16 +7492,16 @@ }, { "name": "symfony/string", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", + "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", "shasum": "" }, "require": { @@ -7559,7 +7559,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.0" + "source": "https://github.com/symfony/string/tree/v7.4.4" }, "funding": [ { @@ -7579,20 +7579,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-01-12T10:54:30+00:00" }, { "name": "symfony/translation", - "version": "v7.4.3", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "7ef27c65d78886f7599fdd5c93d12c9243ecf44d" + "reference": "bfde13711f53f549e73b06d27b35a55207528877" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/7ef27c65d78886f7599fdd5c93d12c9243ecf44d", - "reference": "7ef27c65d78886f7599fdd5c93d12c9243ecf44d", + "url": "https://api.github.com/repos/symfony/translation/zipball/bfde13711f53f549e73b06d27b35a55207528877", + "reference": "bfde13711f53f549e73b06d27b35a55207528877", "shasum": "" }, "require": { @@ -7659,7 +7659,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.3" + "source": "https://github.com/symfony/translation/tree/v7.4.4" }, "funding": [ { @@ -7679,7 +7679,7 @@ "type": "tidelift" } ], - "time": "2025-12-29T09:31:36+00:00" + "time": "2026-01-13T10:40:19+00:00" }, { "name": "symfony/translation-contracts", @@ -7765,16 +7765,16 @@ }, { "name": "symfony/uid", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "2498e9f81b7baa206f44de583f2f48350b90142c" + "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/2498e9f81b7baa206f44de583f2f48350b90142c", - "reference": "2498e9f81b7baa206f44de583f2f48350b90142c", + "url": "https://api.github.com/repos/symfony/uid/zipball/7719ce8aba76be93dfe249192f1fbfa52c588e36", + "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36", "shasum": "" }, "require": { @@ -7819,7 +7819,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.4.0" + "source": "https://github.com/symfony/uid/tree/v7.4.4" }, "funding": [ { @@ -7839,20 +7839,20 @@ "type": "tidelift" } ], - "time": "2025-09-25T11:02:55+00:00" + "time": "2026-01-03T23:30:35+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.4.3", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "7e99bebcb3f90d8721890f2963463280848cba92" + "reference": "0e4769b46a0c3c62390d124635ce59f66874b282" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7e99bebcb3f90d8721890f2963463280848cba92", - "reference": "7e99bebcb3f90d8721890f2963463280848cba92", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e4769b46a0c3c62390d124635ce59f66874b282", + "reference": "0e4769b46a0c3c62390d124635ce59f66874b282", "shasum": "" }, "require": { @@ -7906,7 +7906,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.3" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.4" }, "funding": [ { @@ -7926,7 +7926,7 @@ "type": "tidelift" } ], - "time": "2025-12-18T07:04:31+00:00" + "time": "2026-01-01T22:13:48+00:00" }, { "name": "thecodingmachine/safe", @@ -9036,11 +9036,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.36", + "version": "2.1.37", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2132e5e2361d11d40af4c17faa16f043269a4cf3", - "reference": "2132e5e2361d11d40af4c17faa16f043269a4cf3", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/28cd424c5ea984128c95cfa7ea658808e8954e49", + "reference": "28cd424c5ea984128c95cfa7ea658808e8954e49", "shasum": "" }, "require": { @@ -9085,7 +9085,7 @@ "type": "github" } ], - "time": "2026-01-21T13:58:26+00:00" + "time": "2026-01-24T08:21:55+00:00" }, { "name": "phpunit/php-code-coverage", @@ -9424,16 +9424,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.48", + "version": "11.5.50", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fe3665c15e37140f55aaf658c81a2eb9030b6d89" + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fe3665c15e37140f55aaf658c81a2eb9030b6d89", - "reference": "fe3665c15e37140f55aaf658c81a2eb9030b6d89", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", "shasum": "" }, "require": { @@ -9454,7 +9454,7 @@ "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.3", - "sebastian/comparator": "^6.3.2", + "sebastian/comparator": "^6.3.3", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.1", "sebastian/exporter": "^6.3.2", @@ -9505,7 +9505,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.48" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.50" }, "funding": [ { @@ -9529,7 +9529,7 @@ "type": "tidelift" } ], - "time": "2026-01-16T16:26:27+00:00" + "time": "2026-01-27T05:59:18+00:00" }, { "name": "sebastian/cli-parser", @@ -9703,16 +9703,16 @@ }, { "name": "sebastian/comparator", - "version": "6.3.2", + "version": "6.3.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", - "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", "shasum": "" }, "require": { @@ -9771,7 +9771,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" }, "funding": [ { @@ -9791,7 +9791,7 @@ "type": "tidelift" } ], - "time": "2025-08-10T08:07:46+00:00" + "time": "2026-01-24T09:26:40+00:00" }, { "name": "sebastian/complexity", @@ -10694,16 +10694,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v7.4.1", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "0c5e8f20c74c78172a8ee72b125909b505033597" + "reference": "71fd6a82fc357c8b5de22f78b228acfc43dee965" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/0c5e8f20c74c78172a8ee72b125909b505033597", - "reference": "0c5e8f20c74c78172a8ee72b125909b505033597", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/71fd6a82fc357c8b5de22f78b228acfc43dee965", + "reference": "71fd6a82fc357c8b5de22f78b228acfc43dee965", "shasum": "" }, "require": { @@ -10742,7 +10742,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.4.1" + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.4" }, "funding": [ { @@ -10762,7 +10762,7 @@ "type": "tidelift" } ], - "time": "2025-12-06T15:47:47+00:00" + "time": "2026-01-05T08:47:25+00:00" }, { "name": "theseer/tokenizer", From 9f7d3b55dd770f4e17bc73296fbacd0dbb621b84 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 29 Jan 2026 15:11:40 +0000 Subject: [PATCH 031/204] Updated translations with latest Crowdin changes (#5997) --- lang/ar/errors.php | 1 + lang/bg/errors.php | 1 + lang/bn/errors.php | 1 + lang/bs/errors.php | 1 + lang/ca/errors.php | 1 + lang/cs/errors.php | 1 + lang/cy/errors.php | 1 + lang/da/editor.php | 2 +- lang/da/entities.php | 12 ++--- lang/da/errors.php | 3 +- lang/da/notifications.php | 4 +- lang/da/preferences.php | 2 +- lang/da/settings.php | 12 ++--- lang/da/validation.php | 2 +- lang/de/errors.php | 1 + lang/de_informal/errors.php | 1 + lang/el/errors.php | 1 + lang/es/errors.php | 1 + lang/es_AR/errors.php | 1 + lang/et/errors.php | 1 + lang/eu/errors.php | 1 + lang/fa/errors.php | 1 + lang/fi/errors.php | 1 + lang/fr/errors.php | 1 + lang/he/errors.php | 1 + lang/hr/errors.php | 1 + lang/hu/errors.php | 1 + lang/id/errors.php | 1 + lang/is/errors.php | 1 + lang/it/errors.php | 1 + lang/ja/errors.php | 1 + lang/ka/errors.php | 1 + lang/ko/errors.php | 1 + lang/ku/errors.php | 1 + lang/lt/errors.php | 1 + lang/lv/errors.php | 1 + lang/nb/errors.php | 1 + lang/ne/errors.php | 1 + lang/nl/errors.php | 1 + lang/nn/errors.php | 1 + lang/pl/activities.php | 96 ++++++++++++++++++------------------- lang/pl/common.php | 4 +- lang/pl/editor.php | 10 ++-- lang/pl/entities.php | 70 +++++++++++++-------------- lang/pl/errors.php | 23 ++++----- lang/pl/notifications.php | 4 +- lang/pl/preferences.php | 2 +- lang/pl/settings.php | 80 +++++++++++++++---------------- lang/pl/validation.php | 10 ++-- lang/pt/errors.php | 1 + lang/pt_BR/errors.php | 1 + lang/ro/errors.php | 1 + lang/ru/errors.php | 1 + lang/sk/errors.php | 1 + lang/sl/errors.php | 1 + lang/sq/errors.php | 1 + lang/sr/errors.php | 1 + lang/sv/errors.php | 1 + lang/tk/errors.php | 1 + lang/tr/errors.php | 1 + lang/uk/errors.php | 1 + lang/uz/errors.php | 1 + lang/vi/errors.php | 1 + lang/zh_CN/errors.php | 1 + lang/zh_TW/errors.php | 1 + 65 files changed, 218 insertions(+), 167 deletions(-) diff --git a/lang/ar/errors.php b/lang/ar/errors.php index 491f04c079a..25ba3544d57 100644 --- a/lang/ar/errors.php +++ b/lang/ar/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'الشفرة المُقدمة لرمز API المستخدم المحدد غير صحيحة', 'api_user_no_api_permission' => 'مالك رمز API المستخدم ليس لديه الصلاحية لإجراء مكالمات API', 'api_user_token_expired' => 'انتهت صلاحية رمز الترخيص المستخدم', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'حدث خطأ عند إرسال بريد إلكتروني تجريبي:', diff --git a/lang/bg/errors.php b/lang/bg/errors.php index 9fcda644a1b..6e488718047 100644 --- a/lang/bg/errors.php +++ b/lang/bg/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Секретния код, който беше предоставен за достъп до API-а е неправилен', 'api_user_no_api_permission' => 'Собственика на АPI кода няма право да прави API заявки', 'api_user_token_expired' => 'Кода за достъп, който беше използван, вече не е валиден', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Беше върната грешка, когато се изпрати тестовият емейл:', diff --git a/lang/bn/errors.php b/lang/bn/errors.php index 32dac63e2a8..92414edfdef 100644 --- a/lang/bn/errors.php +++ b/lang/bn/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'The secret provided for the given used API token is incorrect', 'api_user_no_api_permission' => 'The owner of the used API token does not have permission to make API calls', 'api_user_token_expired' => 'The authorization token used has expired', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', diff --git a/lang/bs/errors.php b/lang/bs/errors.php index fc1744805ca..d76f559c17c 100644 --- a/lang/bs/errors.php +++ b/lang/bs/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Tajni ključ naveden za dati korišteni API token nije tačan', 'api_user_no_api_permission' => 'Vlasnik korištenog API tokena nema dozvolu za upućivanje API poziva', 'api_user_token_expired' => 'Autorizacijski token je istekao', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Došlo je do greške prilikom slanja testnog e-maila:', diff --git a/lang/ca/errors.php b/lang/ca/errors.php index 945d6fd0fa6..69268838451 100644 --- a/lang/ca/errors.php +++ b/lang/ca/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'El secret proporcionat per al testimoni d’API utilitzat no és correcte.', 'api_user_no_api_permission' => 'El propietari del testimoni API utilitzat no té permís per a fer crides a l’API.', 'api_user_token_expired' => 'El testimoni d’autorització utilitzat ha caducat.', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'S’ha produït un error en enviar el correu electrònic de prova:', diff --git a/lang/cs/errors.php b/lang/cs/errors.php index 4f9e350257c..17632064208 100644 --- a/lang/cs/errors.php +++ b/lang/cs/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Poskytnutý Token Secret neodpovídá použitému API tokenu', 'api_user_no_api_permission' => 'Vlastník použitého API tokenu nemá oprávnění provádět API volání', 'api_user_token_expired' => 'Platnost autorizačního tokenu vypršela', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Při posílání testovacího e-mailu nastala chyba:', diff --git a/lang/cy/errors.php b/lang/cy/errors.php index f6123c9285a..a3ab3279e41 100644 --- a/lang/cy/errors.php +++ b/lang/cy/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Mae\'r gyfrinach a ddarparwyd ar gyfer y tocyn API defnyddiedig a roddwyd yn anghywir', 'api_user_no_api_permission' => 'Nid oes gan berchennog y tocyn API a ddefnyddiwyd ganiatâd i wneud galwadau API', 'api_user_token_expired' => 'Mae\'r tocyn awdurdodi a ddefnyddiwyd wedi dod i ben', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Gwall a daflwyd wrth anfon e-bost prawf:', diff --git a/lang/da/editor.php b/lang/da/editor.php index 4c8aa0f7212..f135f06a90b 100644 --- a/lang/da/editor.php +++ b/lang/da/editor.php @@ -48,7 +48,7 @@ 'superscript' => 'Hævet', 'subscript' => 'Sænket', 'text_color' => 'Tekstfarve', - 'highlight_color' => 'Highlight color', + 'highlight_color' => 'Fremhævelsesfarve', 'custom_color' => 'Tilpasset farve', 'remove_color' => 'Fjern farve', 'background_color' => 'Baggrundsfarve', diff --git a/lang/da/entities.php b/lang/da/entities.php index 66e3fd2063b..23cc94626c5 100644 --- a/lang/da/entities.php +++ b/lang/da/entities.php @@ -252,7 +252,7 @@ 'pages_edit_switch_to_markdown_stable' => '(Stabilt indhold)', 'pages_edit_switch_to_wysiwyg' => 'Skift til WYSIWYG redigering', 'pages_edit_switch_to_new_wysiwyg' => 'Skift til ny WYSIWYG (Hvad man ser, er hvad man får)', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(I Beta Test)', 'pages_edit_set_changelog' => 'Sæt ændringsoversigt', 'pages_edit_enter_changelog_desc' => 'Indtast en kort beskrivelse af ændringer du har lavet', 'pages_edit_enter_changelog' => 'Indtast ændringsoversigt', @@ -397,11 +397,11 @@ 'comment' => 'Kommentar', 'comments' => 'Kommentarer', 'comment_add' => 'Tilføj kommentar', - 'comment_none' => 'No comments to display', + 'comment_none' => 'Ingen kommentarer at vise', 'comment_placeholder' => 'Skriv en kommentar her', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', + 'comment_thread_count' => ':count Kommentar Tråde:count Kommentar Tråde', 'comment_archived_count' => ':count Arkiveret', - 'comment_archived_threads' => 'Archived Threads', + 'comment_archived_threads' => 'Arkiverede Tråde', 'comment_save' => 'Gem kommentar', 'comment_new' => 'Ny kommentar', 'comment_created' => 'kommenteret :createDiff', @@ -410,8 +410,8 @@ 'comment_deleted_success' => 'Kommentar slettet', 'comment_created_success' => 'Kommentaren er tilføjet', 'comment_updated_success' => 'Kommentaren er opdateret', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', + 'comment_archive_success' => 'Kommentar arkiveret', + 'comment_unarchive_success' => 'Kommentaren er ikke længere arkiveret', 'comment_view' => 'Se kommentar', 'comment_jump_to_thread' => 'Hop til tråd', 'comment_delete_confirm' => 'Er du sikker på, at du vil slette denne kommentar?', diff --git a/lang/da/errors.php b/lang/da/errors.php index 6f9d1d53cf2..eeb96f59b8d 100644 --- a/lang/da/errors.php +++ b/lang/da/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => 'Kunne ikke læse ZIP-filen.', 'import_zip_cant_decode_data' => 'Kunne ikke finde og afkode ZIP data.json-indhold.', 'import_zip_no_data' => 'ZIP-filens data har ikke noget forventet bog-, kapitel- eller sideindhold.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'Indholdet af ZIP data.json overstiger den konfigurerede maksimale uploadstørrelse for applikationen.', 'import_validation_failed' => 'Import ZIP kunne ikke valideres med fejl:', 'import_zip_failed_notification' => 'Kunne ikke importere ZIP-fil.', 'import_perms_books' => 'Du mangler de nødvendige tilladelser til at oprette bøger.', @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Hemmeligheden leveret til det givne anvendte API-token er forkert', 'api_user_no_api_permission' => 'Ejeren af den brugte API token har ikke adgang til at foretage API-kald', 'api_user_token_expired' => 'Den brugte godkendelsestoken er udløbet', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Følgende fejl opstod under afsendelse af testemail:', diff --git a/lang/da/notifications.php b/lang/da/notifications.php index 3aaf2ff1818..fc4d7d763b7 100644 --- a/lang/da/notifications.php +++ b/lang/da/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => 'Opdateret side: :pageName', 'updated_page_intro' => 'En side er blevet opdateret i :appName:', 'updated_page_debounce' => 'For at forhindre en masse af notifikationer, i et stykke tid vil du ikke blive sendt notifikationer for yderligere redigeringer til denne side af den samme editor.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'Du er blevet nævnt i en kommentar på siden: :pageName', + 'comment_mention_intro' => 'Du blev nævnt i en kommentar på :appName:', 'detail_page_name' => 'Sidens navn:', 'detail_page_path' => 'Sidesti:', diff --git a/lang/da/preferences.php b/lang/da/preferences.php index 5ba4f450cab..9b31a0c1f16 100644 --- a/lang/da/preferences.php +++ b/lang/da/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'Administrer de e-mail-notifikationer, du modtager, når visse aktiviteter udføres i systemet.', 'notifications_opt_own_page_changes' => 'Adviser ved ændringer af sider, jeg ejer', 'notifications_opt_own_page_comments' => 'Adviser ved kommentarer på sider, jeg ejer', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'Giv besked, når jeg er nævnt i en kommentar', 'notifications_opt_comment_replies' => 'Adviser ved svar på mine kommentarer', 'notifications_save' => 'Gem indstillinger', 'notifications_update_success' => 'Indstillinger for notifikationer er blevet opdateret!', diff --git a/lang/da/settings.php b/lang/da/settings.php index 8d89c30af2d..2f161ed4fe4 100644 --- a/lang/da/settings.php +++ b/lang/da/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Ingen restriktion opsat', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', + 'sorting' => 'Lister & Sortering', + 'sorting_book_default' => 'Standardregel for sortering af bog', 'sorting_book_default_desc' => 'Vælg den standardsorteringsregel, der skal gælde for nye bøger. Dette påvirker ikke eksisterende bøger og kan tilsidesættes for hver enkelt bog.', 'sorting_rules' => 'Regler for sortering', 'sorting_rules_desc' => 'Det er foruddefinerede sorteringsoperationer, som kan anvendes på indhold i systemet.', @@ -103,8 +103,8 @@ 'sort_rule_op_updated_date' => 'Opdateret dato', 'sort_rule_op_chapters_first' => 'Kapitler først', 'sort_rule_op_chapters_last' => 'De sidste kapitler', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits' => 'Visningsgrænser pr. side', + 'sorting_page_limits_desc' => 'Angiv, hvor mange elementer der skal vises pr. side i forskellige lister i systemet. Typisk vil et lavere beløb være mere effektivt, mens et højere beløb undgår behovet for at klikke sig igennem flere sider. Det anbefales at bruge et lige multiplum af 3 (18, 24, 30 osv.).', // Maintenance settings 'maint' => 'Vedligeholdelse', @@ -197,13 +197,13 @@ 'role_import_content' => 'Importer indhold', 'role_editor_change' => 'Skift side editor', 'role_notifications' => 'Modtag og administrer notifikationer', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => 'Disse tilladelser vil teknisk set også give synlighed og søgning efter brugere og roller i systemet.', 'role_asset' => 'Tilladelser for medier og "assets"', 'roles_system_warning' => 'Vær opmærksom på, at adgang til alle af de ovennævnte tre tilladelser, kan give en bruger mulighed for at ændre deres egne brugerrettigheder eller brugerrettigheder for andre i systemet. Tildel kun roller med disse tilladelser til betroede brugere.', 'role_asset_desc' => 'Disse tilladelser kontrollerer standardadgang til medier og "assets" i systemet. Tilladelser til bøger, kapitler og sider tilsidesætter disse tilladelser.', 'role_asset_admins' => 'Administratorer får automatisk adgang til alt indhold, men disse indstillinger kan vise eller skjule UI-indstillinger.', 'role_asset_image_view_note' => 'Dette vedrører synlighed i billedhåndteringen. Den faktiske adgang til uploadede billedfiler vil afhænge af systemets billedlagringsindstilling.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Disse tilladelser vil teknisk set også give synlighed og søgning efter brugere i systemet.', 'role_all' => 'Alle', 'role_own' => 'Eget', 'role_controlled_by_asset' => 'Styres af det medie/"asset", de uploades til', diff --git a/lang/da/validation.php b/lang/da/validation.php index 36b9b49fb3f..58e80052a63 100644 --- a/lang/da/validation.php +++ b/lang/da/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'Filen kunne ikke oploades. Serveren accepterer muligvis ikke filer af denne størrelse.', 'zip_file' => 'Attributten skal henvise til en fil i ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => 'Filen :attribute må ikke overstige: størrelse MB.', 'zip_file_mime' => 'Attributten skal henvise til en fil af typen: validTypes, fundet:foundType.', 'zip_model_expected' => 'Data objekt forventet men ":type" fundet.', 'zip_unique' => 'Attributten skal være unik for objekttypen i ZIP.', diff --git a/lang/de/errors.php b/lang/de/errors.php index 56dc6d59eb0..75c1cf3944e 100644 --- a/lang/de/errors.php +++ b/lang/de/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Das Kennwort für das angegebene API-Token ist falsch', 'api_user_no_api_permission' => 'Der Besitzer des verwendeten API-Tokens hat keine Berechtigung für API-Aufrufe', 'api_user_token_expired' => 'Das verwendete Autorisierungstoken ist abgelaufen', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Fehler beim Versenden einer Test E-Mail:', diff --git a/lang/de_informal/errors.php b/lang/de_informal/errors.php index cd14dd92915..a3be19a028d 100644 --- a/lang/de_informal/errors.php +++ b/lang/de_informal/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Das für den API-Token angegebene geheime Token ist falsch', 'api_user_no_api_permission' => 'Der Besitzer des verwendeten API-Token hat keine Berechtigung für API-Aufrufe', 'api_user_token_expired' => 'Das verwendete Autorisierungs-Token ist abgelaufen', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Fehler beim Senden einer Test E-Mail:', diff --git a/lang/el/errors.php b/lang/el/errors.php index fb13ec2fb05..f03ed2953d7 100644 --- a/lang/el/errors.php +++ b/lang/el/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Το μυστικό που παρέχεται για το δεδομένο χρησιμοποιημένο διακριτικό API είναι εσφαλμένο', 'api_user_no_api_permission' => 'Ο ιδιοκτήτης του χρησιμοποιημένου διακριτικού API δεν έχει άδεια για να κάνει κλήσεις API', 'api_user_token_expired' => 'Το διακριτικό εξουσιοδότησης που χρησιμοποιείται έχει λήξει', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Σφάλμα κατά την αποστολή δοκιμαστικού email:', diff --git a/lang/es/errors.php b/lang/es/errors.php index 2cbe7b9edc1..76581257e8c 100644 --- a/lang/es/errors.php +++ b/lang/es/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'El secreto proporcionado para el token API usado es incorrecto', 'api_user_no_api_permission' => 'El propietario del token API usado no tiene permiso para hacer llamadas API', 'api_user_token_expired' => 'El token de autorización usado ha caducado', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Error al enviar un email de prueba:', diff --git a/lang/es_AR/errors.php b/lang/es_AR/errors.php index 08745cf30da..5e052c51d5a 100644 --- a/lang/es_AR/errors.php +++ b/lang/es_AR/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'El secreto proporcionado para el token API usado es incorrecto', 'api_user_no_api_permission' => 'El propietario del token API usado no tiene permiso para hacer llamadas API', 'api_user_token_expired' => 'El token de autorización usado ha caducado', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Error al enviar un email de prueba:', diff --git a/lang/et/errors.php b/lang/et/errors.php index 8ee50f4f78b..c9b9fbb4e8a 100644 --- a/lang/et/errors.php +++ b/lang/et/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'API tunnusele lisatud salajane võti ei ole korrektne', 'api_user_no_api_permission' => 'Selle API tunnuse omanikul ei ole õigust API päringuid teha', 'api_user_token_expired' => 'Volitustunnus on aegunud', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Test e-kirja saatmisel tekkis viga:', diff --git a/lang/eu/errors.php b/lang/eu/errors.php index 822c6482944..5b011368ae6 100644 --- a/lang/eu/errors.php +++ b/lang/eu/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'The secret provided for the given used API token is incorrect', 'api_user_no_api_permission' => 'The owner of the used API token does not have permission to make API calls', 'api_user_token_expired' => 'The authorization token used has expired', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', diff --git a/lang/fa/errors.php b/lang/fa/errors.php index d357456159c..777d6e69293 100644 --- a/lang/fa/errors.php +++ b/lang/fa/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'راز ارائه شده برای کد API استفاده شده نادرست است', 'api_user_no_api_permission' => 'مالک نشانه API استفاده شده اجازه برقراری تماس های API را ندارد', 'api_user_token_expired' => 'رمز مجوز استفاده شده منقضی شده است', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'خطا در هنگام ارسال ایمیل آزمایشی:', diff --git a/lang/fi/errors.php b/lang/fi/errors.php index f470ce614c4..a51edf5bec9 100644 --- a/lang/fi/errors.php +++ b/lang/fi/errors.php @@ -126,6 +126,7 @@ 'api_incorrect_token_secret' => 'API-tunnisteelle annettu salainen avain on virheellinen', 'api_user_no_api_permission' => 'Käytetyn API-tunnisteen omistajalla ei ole oikeutta tehdä API-kutsuja', 'api_user_token_expired' => 'Käytetty valtuutuskoodi on vanhentunut', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Virhe testisähköpostia lähetettäessä:', diff --git a/lang/fr/errors.php b/lang/fr/errors.php index 0bc8e491765..a5a3614c2f9 100644 --- a/lang/fr/errors.php +++ b/lang/fr/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Le secret fourni pour le jeton d\'API utilisé est incorrect', 'api_user_no_api_permission' => 'Le propriétaire du jeton API utilisé n\'a pas la permission de passer des requêtes API', 'api_user_token_expired' => 'Le jeton d\'autorisation utilisé a expiré', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Erreur émise lors de l\'envoi d\'un e-mail de test :', diff --git a/lang/he/errors.php b/lang/he/errors.php index a2bc86ae492..99249fb516e 100644 --- a/lang/he/errors.php +++ b/lang/he/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'The secret provided for the given used API token is incorrect', 'api_user_no_api_permission' => 'The owner of the used API token does not have permission to make API calls', 'api_user_token_expired' => 'The authorization token used has expired', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', diff --git a/lang/hr/errors.php b/lang/hr/errors.php index ad1b2668f53..298fcb81ba2 100644 --- a/lang/hr/errors.php +++ b/lang/hr/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Netočan API token', 'api_user_no_api_permission' => 'Vlasnik API tokena nema potrebna dopuštenja', 'api_user_token_expired' => 'Autorizacija je istekla', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Pogreška prilikom slanja testnog email:', diff --git a/lang/hu/errors.php b/lang/hu/errors.php index 10e3adbf9aa..4264af8ab67 100644 --- a/lang/hu/errors.php +++ b/lang/hu/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Az API tokenhez használt secret helytelen', 'api_user_no_api_permission' => 'A használt API vezérjel tulajdonosának nincs jogosultsága API hívások végrehajtásához', 'api_user_token_expired' => 'A használt hitelesítési vezérjel lejárt', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Hiba történt egy teszt email küldésekor:', diff --git a/lang/id/errors.php b/lang/id/errors.php index 77254783108..2342824d107 100644 --- a/lang/id/errors.php +++ b/lang/id/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Rahasia yang diberikan untuk token API bekas yang diberikan salah', 'api_user_no_api_permission' => 'Pemilik token API yang digunakan tidak memiliki izin untuk melakukan panggilan API', 'api_user_token_expired' => 'Token otorisasi yang digunakan telah kedaluwarsa', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Kesalahan dilempar saat mengirim email uji:', diff --git a/lang/is/errors.php b/lang/is/errors.php index a29c8475a7b..48b82cf7eb5 100644 --- a/lang/is/errors.php +++ b/lang/is/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Leyndarmálið sem gefið var upp fyrir API tókann er rangt', 'api_user_no_api_permission' => 'Eigandi API tókans hefur ekki heimild til að gera API köll', 'api_user_token_expired' => 'Auðkenningar tókin er útrunninn', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Villa kom upp viðað reyna senda prufu tölvupóst:', diff --git a/lang/it/errors.php b/lang/it/errors.php index 62f294842e1..99700088b29 100644 --- a/lang/it/errors.php +++ b/lang/it/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Il token segreto fornito per il token API utilizzato non è corretto', 'api_user_no_api_permission' => 'Il proprietario del token API utilizzato non ha il permesso di effettuare chiamate API', 'api_user_token_expired' => 'Il token di autorizzazione utilizzato è scaduto', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Si è verificato un errore durante l\'invio di una e-mail di prova:', diff --git a/lang/ja/errors.php b/lang/ja/errors.php index cad2e5486f9..7ce8db11100 100644 --- a/lang/ja/errors.php +++ b/lang/ja/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => '利用されたAPIトークンに対して提供されたシークレットが正しくありません', 'api_user_no_api_permission' => '使用されているAPIトークンの所有者には、API呼び出しを行う権限がありません', 'api_user_token_expired' => '認証トークンが期限切れです。', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'テストメール送信時にエラーが発生しました:', diff --git a/lang/ka/errors.php b/lang/ka/errors.php index 77d7ee69e49..20537d59f0c 100644 --- a/lang/ka/errors.php +++ b/lang/ka/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'The secret provided for the given used API token is incorrect', 'api_user_no_api_permission' => 'The owner of the used API token does not have permission to make API calls', 'api_user_token_expired' => 'The authorization token used has expired', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', diff --git a/lang/ko/errors.php b/lang/ko/errors.php index 12ee2697a41..9fa56ffcdc3 100644 --- a/lang/ko/errors.php +++ b/lang/ko/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'API 토큰이 제공한 암호에 문제가 있습니다.', 'api_user_no_api_permission' => 'API 토큰의 소유자가 API를 호출할 권한이 없습니다.', 'api_user_token_expired' => '인증 토큰이 만료되었습니다.', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => '메일을 발송하는 도중 문제가 생겼습니다:', diff --git a/lang/ku/errors.php b/lang/ku/errors.php index 77d7ee69e49..20537d59f0c 100644 --- a/lang/ku/errors.php +++ b/lang/ku/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'The secret provided for the given used API token is incorrect', 'api_user_no_api_permission' => 'The owner of the used API token does not have permission to make API calls', 'api_user_token_expired' => 'The authorization token used has expired', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', diff --git a/lang/lt/errors.php b/lang/lt/errors.php index f2917058eab..c962c27f2f5 100644 --- a/lang/lt/errors.php +++ b/lang/lt/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Pateiktas panaudoto API žetono slėpinys yra neteisingas', 'api_user_no_api_permission' => 'API prieigos rakto savininkas neturi leidimo daryti API skambučius', 'api_user_token_expired' => 'Prieigos rakto naudojimas baigė galioti', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Siunčiant bandymo email: įvyko klaida', diff --git a/lang/lv/errors.php b/lang/lv/errors.php index b4e9ec61aaf..a11b5754c9c 100644 --- a/lang/lv/errors.php +++ b/lang/lv/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Norādītā slepenā atslēga izmantotajam API žetonam nav pareiza', 'api_user_no_api_permission' => 'Izmantotā API žetona īpašniekam nav tiesības veikt API izsaukumus', 'api_user_token_expired' => 'Autorizācijas žetona derīguma termiņš ir izbeidzies', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Radusies kļūda sūtot testa epastu:', diff --git a/lang/nb/errors.php b/lang/nb/errors.php index 91368314c98..111a20418cd 100644 --- a/lang/nb/errors.php +++ b/lang/nb/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Hemmeligheten som er gitt for det gitte brukte API-tokenet er feil', 'api_user_no_api_permission' => 'Eieren av det brukte API-tokenet har ikke tillatelse til å ringe API-samtaler', 'api_user_token_expired' => 'Autorisasjonstokenet som er brukt, har utløpt', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Feil kastet når du sendte en test-e-post:', diff --git a/lang/ne/errors.php b/lang/ne/errors.php index bbcaec9e180..d221f610434 100644 --- a/lang/ne/errors.php +++ b/lang/ne/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'दिइएको API टोकनको लागि प्रदान गरिएको गोप्य सही छैन।', 'api_user_no_api_permission' => 'API टोकनको मालिकसँग API कल गर्ने अनुमति छैन।', 'api_user_token_expired' => 'प्रमाणीकरण टोकन समाप्त भइसकेको छ।', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'टेस्ट इमेल पठाउँदा त्रुटि:', diff --git a/lang/nl/errors.php b/lang/nl/errors.php index c1de7b36e8c..0e478fd5ac6 100644 --- a/lang/nl/errors.php +++ b/lang/nl/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Het opgegeven geheim voor de API-token is onjuist', 'api_user_no_api_permission' => 'De eigenaar van de gebruikte API-token heeft geen machtiging om API calls te maken', 'api_user_token_expired' => 'De gebruikte autorisatie token is verlopen', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Fout opgetreden bij het verzenden van een test email:', diff --git a/lang/nn/errors.php b/lang/nn/errors.php index 01d83e0ac01..31a08c2c2a8 100644 --- a/lang/nn/errors.php +++ b/lang/nn/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Hemmeligheten som er gitt for det gitte brukte API-tokenet er feil', 'api_user_no_api_permission' => 'Eieren av det brukte API-tokenet har ikke tillatelse til å ringe API-samtaler', 'api_user_token_expired' => 'Autorisasjonstokenet som er brukt, har utløpt', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Feil kastet når du sendte en test-e-post:', diff --git a/lang/pl/activities.php b/lang/pl/activities.php index dc078ad57ab..323b7035e1b 100644 --- a/lang/pl/activities.php +++ b/lang/pl/activities.php @@ -6,52 +6,52 @@ return [ // Pages - 'page_create' => 'utworzył stronę', + 'page_create' => 'utworzono stronę', 'page_create_notification' => 'Strona została utworzona', - 'page_update' => 'zaktualizował stronę', + 'page_update' => 'zaktualizowano stronę', 'page_update_notification' => 'Strona zaktualizowana pomyślnie', - 'page_delete' => 'usunął stronę', + 'page_delete' => 'usunięto stronę', 'page_delete_notification' => 'Strona została usunięta', - 'page_restore' => 'przywrócił stronę', + 'page_restore' => 'przywrócono stronę', 'page_restore_notification' => 'Strona przywrócona pomyślnie', - 'page_move' => 'przeniósł stronę', + 'page_move' => 'przeniesiono stronę', 'page_move_notification' => 'Strona przeniesiona pomyślnie', // Chapters - 'chapter_create' => 'utworzył rozdział', + 'chapter_create' => 'utworzono rozdział', 'chapter_create_notification' => 'Rozdział utworzony pomyślnie', - 'chapter_update' => 'zaktualizował rozdział', + 'chapter_update' => 'zaktualizowano rozdział', 'chapter_update_notification' => 'Rozdział zaktualizowany pomyślnie', - 'chapter_delete' => 'usunął rozdział', + 'chapter_delete' => 'usunięto rozdział', 'chapter_delete_notification' => 'Rozdział usunięty pomyślnie', - 'chapter_move' => 'przeniósł rozdział', + 'chapter_move' => 'przeniesiono rozdział', 'chapter_move_notification' => 'Rozdział przeniesiony pomyślnie', // Books - 'book_create' => 'utworzył książkę', + 'book_create' => 'utworzono książkę', 'book_create_notification' => 'Książka utworzona pomyślnie', - 'book_create_from_chapter' => 'skonwertował rozdział na książkę', + 'book_create_from_chapter' => 'przekonwertowano rozdział na książkę', 'book_create_from_chapter_notification' => 'Rozdział został pomyślnie skonwertowany do książki', - 'book_update' => 'zaktualizował książkę', + 'book_update' => 'zaktualizowano książkę', 'book_update_notification' => 'Książka zaktualizowana pomyślnie', - 'book_delete' => 'usunął książkę', + 'book_delete' => 'usunięto książkę', 'book_delete_notification' => 'Książka usunięta pomyślnie', - 'book_sort' => 'posortował książkę', + 'book_sort' => 'posortowano książkę', 'book_sort_notification' => 'Książka posortowana pomyślnie', // Bookshelves - 'bookshelf_create' => 'utworzył półkę', + 'bookshelf_create' => 'utworzyono półkę', 'bookshelf_create_notification' => 'Półka utworzona pomyślnie', - 'bookshelf_create_from_book' => 'skonwertował książkę na półkę', + 'bookshelf_create_from_book' => 'przekonwertowano książkę na półkę', 'bookshelf_create_from_book_notification' => 'Książka została pomyślnie skonwertowana na półkę', - 'bookshelf_update' => 'zaktualizował półkę', + 'bookshelf_update' => 'zaktualizowano półkę', 'bookshelf_update_notification' => 'Półka zaktualizowana pomyślnie', - 'bookshelf_delete' => 'usunął półkę', + 'bookshelf_delete' => 'usunięto półkę', 'bookshelf_delete_notification' => 'Półka usunięta pomyślnie', // Revisions - 'revision_restore' => 'przywrócił wersję', - 'revision_delete' => 'usunął wersję', + 'revision_restore' => 'przywrócono wersję', + 'revision_delete' => 'usunięto wersję', 'revision_delete_notification' => 'Wersja usunięta pomyślnie', // Favourites @@ -72,54 +72,54 @@ 'mfa_remove_method_notification' => 'Metoda wieloskładnikowa pomyślnie usunięta', // Settings - 'settings_update' => 'zaktualizował ustawienia', + 'settings_update' => 'zaktualizowano ustawienia', 'settings_update_notification' => 'Ustawienia zaktualizowane pomyślnie', - 'maintenance_action_run' => 'uruchomił akcję konserwacji', + 'maintenance_action_run' => 'uruchomiono akcję konserwacji', // Webhooks - 'webhook_create' => 'utworzył webhook', + 'webhook_create' => 'utworzono webhook', 'webhook_create_notification' => 'Webhook utworzony pomyślnie', - 'webhook_update' => 'zaktualizował webhook', + 'webhook_update' => 'zaktualizowano webhook', 'webhook_update_notification' => 'Webhook zaktualizowany pomyślnie', - 'webhook_delete' => 'usunął webhook', + 'webhook_delete' => 'usunięto webhook', 'webhook_delete_notification' => 'Webhook usunięty pomyślnie', // Imports 'import_create' => 'utworzono import', - 'import_create_notification' => 'Import successfully uploaded', - 'import_run' => 'updated import', - 'import_run_notification' => 'Content successfully imported', - 'import_delete' => 'deleted import', - 'import_delete_notification' => 'Import successfully deleted', + 'import_create_notification' => 'Import zakończony sukcesem', + 'import_run' => 'zaktualizowano import', + 'import_run_notification' => 'Zawartość pomyślnie zaimportowana', + 'import_delete' => 'usunięto import', + 'import_delete_notification' => 'Import usunięty', // Users - 'user_create' => 'utworzył użytkownika', + 'user_create' => 'utworzono użytkownika', 'user_create_notification' => 'Użytkownik utworzony pomyślnie', - 'user_update' => 'zaktualizował użytkownika', + 'user_update' => 'zaktualizowano użytkownika', 'user_update_notification' => 'Użytkownik zaktualizowany pomyślnie', - 'user_delete' => 'usunął użytkownika', + 'user_delete' => 'usunięto użytkownika', 'user_delete_notification' => 'Użytkownik pomyślnie usunięty', // API Tokens - 'api_token_create' => 'utworzył token API', + 'api_token_create' => 'utworzono token API', 'api_token_create_notification' => 'Token API został poprawnie utworzony', - 'api_token_update' => 'zaktualizował token API', + 'api_token_update' => 'zaktualizowano token API', 'api_token_update_notification' => 'Token API został pomyślnie zaktualizowany', - 'api_token_delete' => 'usunął token API', + 'api_token_delete' => 'usunięto token API', 'api_token_delete_notification' => 'Token API został pomyślnie usunięty', // Roles - 'role_create' => 'utworzył rolę', + 'role_create' => 'utworzono rolę', 'role_create_notification' => 'Rola utworzona pomyślnie', - 'role_update' => 'zaktualizował rolę', + 'role_update' => 'zaktualizowano rolę', 'role_update_notification' => 'Rola zaktualizowana pomyślnie', - 'role_delete' => 'usunął rolę', + 'role_delete' => 'usunięto rolę', 'role_delete_notification' => 'Rola usunięta pomyślnie', // Recycle Bin - 'recycle_bin_empty' => 'opróżnił kosz', - 'recycle_bin_restore' => 'przywrócił z kosza', - 'recycle_bin_destroy' => 'usunął z kosza', + 'recycle_bin_empty' => 'opróżniono kosz', + 'recycle_bin_restore' => 'przywrócono z kosza', + 'recycle_bin_destroy' => 'usunięto z kosza', // Comments 'commented_on' => 'skomentował', @@ -128,12 +128,12 @@ 'comment_delete' => 'usunął komentarz', // Sort Rules - 'sort_rule_create' => 'created sort rule', - 'sort_rule_create_notification' => 'Sort rule successfully created', - 'sort_rule_update' => 'updated sort rule', - 'sort_rule_update_notification' => 'Sort rule successfully updated', - 'sort_rule_delete' => 'deleted sort rule', - 'sort_rule_delete_notification' => 'Sort rule successfully deleted', + 'sort_rule_create' => 'utworzono regułę sortowania', + 'sort_rule_create_notification' => 'Reguła sortowania została pomyślnie stworzona', + 'sort_rule_update' => 'zaktualizowano regułę sortowania', + 'sort_rule_update_notification' => 'Reguła sortowania została pomyślnie zaktualizowana', + 'sort_rule_delete' => 'usunięto regułę sortowania', + 'sort_rule_delete_notification' => 'Reguła sortowania została pomyślnie usunięta', // Other 'permissions_update' => 'zaktualizował uprawnienia', diff --git a/lang/pl/common.php b/lang/pl/common.php index e21302928b6..1f795ea8296 100644 --- a/lang/pl/common.php +++ b/lang/pl/common.php @@ -30,8 +30,8 @@ 'create' => 'Utwórz', 'update' => 'Zaktualizuj', 'edit' => 'Edytuj', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'Archiwizuj', + 'unarchive' => 'Wypakuj z archiwum', 'sort' => 'Sortuj', 'move' => 'Przenieś', 'copy' => 'Skopiuj', diff --git a/lang/pl/editor.php b/lang/pl/editor.php index a93b5dd7880..02206ba476c 100644 --- a/lang/pl/editor.php +++ b/lang/pl/editor.php @@ -13,7 +13,7 @@ 'cancel' => 'Anuluj', 'save' => 'Zapisz', 'close' => 'Zamknij', - 'apply' => 'Apply', + 'apply' => 'Zatwierdź', 'undo' => 'Cofnij', 'redo' => 'Ponów', 'left' => 'Lewa strona', @@ -48,7 +48,7 @@ 'superscript' => 'Indeks górny', 'subscript' => 'Indeks dolny', 'text_color' => 'Kolor tekstu', - 'highlight_color' => 'Highlight color', + 'highlight_color' => 'Kolor podkreślenia', 'custom_color' => 'Kolor niestandardowy', 'remove_color' => 'Usuń kolor', 'background_color' => 'Kolor tła', @@ -149,7 +149,7 @@ 'url' => 'Adres URL', 'text_to_display' => 'Tekst do wyświetlenia', 'title' => 'Tytuł', - 'browse_links' => 'Browse links', + 'browse_links' => 'Przeglądaj linki', 'open_link' => 'Otwórz link', 'open_link_in' => 'Otwórz link w...', 'open_link_current' => 'Bieżące okno', @@ -166,8 +166,8 @@ 'about' => 'O edytorze', 'about_title' => 'O edytorze WYSIWYG', 'editor_license' => 'Licencja edytora i prawa autorskie', - 'editor_lexical_license' => 'This editor is built as a fork of :lexicalLink which is distributed under the MIT license.', - 'editor_lexical_license_link' => 'Full license details can be found here.', + 'editor_lexical_license' => 'Ten edytor został zbudowany na podstawie :lexicalLink, który jest dystrybuowany na licencji MIT.', + 'editor_lexical_license_link' => 'Pełne szczegóły licencji znajdziesz tutaj.', 'editor_tiny_license' => 'Ten edytor jest zbudowany przy użyciu :tinyLink, który jest udostępniany na licencji MIT.', 'editor_tiny_license_link' => 'Szczegóły dotyczące praw autorskich i licencji TinyMCE można znaleźć tutaj.', 'save_continue' => 'Zapisz stronę i kontynuuj', diff --git a/lang/pl/entities.php b/lang/pl/entities.php index 72389500b65..f3ad807af29 100644 --- a/lang/pl/entities.php +++ b/lang/pl/entities.php @@ -39,30 +39,30 @@ 'export_pdf' => 'Plik PDF', 'export_text' => 'Plik tekstowy', 'export_md' => 'Pliki Markdown', - 'export_zip' => 'Portable ZIP', + 'export_zip' => 'Archiwum ZIP', 'default_template' => 'Domyślny szablon strony', 'default_template_explain' => 'Przypisz szablon strony, który będzie używany jako domyślna zawartość dla wszystkich stron utworzonych w tym elemencie. Pamiętaj, że będzie to używane tylko wtedy, gdy twórca strony ma dostęp do wybranej strony szablonu.', 'default_template_select' => 'Wybierz stronę szablonu', - 'import' => 'Import', - 'import_validate' => 'Validate Import', - 'import_desc' => 'Import books, chapters & pages using a portable zip export from the same, or a different, instance. Select a ZIP file to proceed. After the file has been uploaded and validated you\'ll be able to configure & confirm the import in the next view.', - 'import_zip_select' => 'Select ZIP file to upload', - 'import_zip_validation_errors' => 'Errors were detected while validating the provided ZIP file:', - 'import_pending' => 'Pending Imports', - 'import_pending_none' => 'No imports have been started.', - 'import_continue' => 'Continue Import', - 'import_continue_desc' => 'Review the content due to be imported from the uploaded ZIP file. When ready, run the import to add its contents to this system. The uploaded ZIP import file will be automatically removed on successful import.', - 'import_details' => 'Import Details', - 'import_run' => 'Run Import', - 'import_size' => ':size Import ZIP Size', - 'import_uploaded_at' => 'Uploaded :relativeTime', - 'import_uploaded_by' => 'Uploaded by', - 'import_location' => 'Import Location', - 'import_location_desc' => 'Select a target location for your imported content. You\'ll need the relevant permissions to create within the location you choose.', - 'import_delete_confirm' => 'Are you sure you want to delete this import?', - 'import_delete_desc' => 'This will delete the uploaded import ZIP file, and cannot be undone.', - 'import_errors' => 'Import Errors', - 'import_errors_desc' => 'The follow errors occurred during the import attempt:', + 'import' => 'Importuj', + 'import_validate' => 'Zweryfikuj import', + 'import_desc' => 'Importuj książki, rozdziały i strony za pomocą eksportu archiwum ZIP z tej samej lub innej instancji. Wybierz plik ZIP, aby kontynuować. Po przesłaniu i potwierdzeniu pliku będziesz mógł skonfigurować i potwierdzić import w następnym kroku.', + 'import_zip_select' => 'Wybierz archiwum ZIP do wgrania', + 'import_zip_validation_errors' => 'Podczas sprawdzania poprawności dostarczonego pliku ZIP wykryto błędy:', + 'import_pending' => 'Oczekujące importy', + 'import_pending_none' => 'Żaden import nie został uruchomiony.', + 'import_continue' => 'Kontynuuj import', + 'import_continue_desc' => 'Przejrzyj zawartość, która ma być zaimportowana z przesłanego pliku ZIP. Kiedy będziesz gotowy, uruchom import, aby dodać jego zawartość do systemu. Przesłane archiwum ZIP zostanie automatycznie usunięte po udanym importowaniu.', + 'import_details' => 'Szczegóły importu', + 'import_run' => 'Wykonaj import', + 'import_size' => ':size wielkość importu ZIP', + 'import_uploaded_at' => 'Przesłano :relativeTime', + 'import_uploaded_by' => 'Przesłane przez', + 'import_location' => 'Lokalizacja importu', + 'import_location_desc' => 'Wybierz docelową lokalizację dla importowanej zawartości. Będziesz potrzebować odpowiednich uprawnień do tworzenia w wybranej lokalizacji.', + 'import_delete_confirm' => 'Czy na pewno chcesz usunąć ten import?', + 'import_delete_desc' => 'Spowoduje to usunięcie zaimportowanego archiwum ZIP. Tej operacji nie da się cofnąć.', + 'import_errors' => 'Błędy importu', + 'import_errors_desc' => 'Podczas próby importu wystąpiły następujące błędy:', 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter', 'breadcrumb_siblings_for_book' => 'Navigate siblings for book', @@ -171,8 +171,8 @@ 'books_navigation' => 'Nawigacja po książce', 'books_sort' => 'Sortuj zawartość książki', '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' => 'Opcja automatycznego sortowania', + 'books_sort_auto_sort_active' => 'Automatyczne sortowanie aktywne: :sortName', 'books_sort_named' => 'Sortuj książkę :bookName', 'books_sort_name' => 'Sortuj według nazwy', 'books_sort_created' => 'Sortuj według daty utworzenia', @@ -252,7 +252,7 @@ 'pages_edit_switch_to_markdown_stable' => '(Statyczna zawartość)', 'pages_edit_switch_to_wysiwyg' => 'Przełącz na edytor WYSIWYG', 'pages_edit_switch_to_new_wysiwyg' => 'Przełącz na nowy WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(W testach beta)', 'pages_edit_set_changelog' => 'Ustaw dziennik zmian', 'pages_edit_enter_changelog_desc' => 'Opisz zmiany, które zostały wprowadzone', 'pages_edit_enter_changelog' => 'Wyświetl dziennik zmian', @@ -272,7 +272,7 @@ 'pages_md_insert_drawing' => 'Wstaw rysunek', 'pages_md_show_preview' => 'Pokaż podgląd', 'pages_md_sync_scroll' => 'Synchronizuj przewijanie podglądu', - 'pages_md_plain_editor' => 'Plaintext editor', + 'pages_md_plain_editor' => 'Zwykły edytor', 'pages_drawing_unsaved' => 'Znaleziono niezapisany rysunek', 'pages_drawing_unsaved_confirm' => 'Znaleziono niezapisane dane rysowania z poprzedniej nieudanej próby zapisu. Czy chcesz przywrócić i kontynuować edycję tego niezapisanego rysunku?', 'pages_not_in_chapter' => 'Strona nie została umieszczona w rozdziale', @@ -397,11 +397,11 @@ 'comment' => 'Komentarz', 'comments' => 'Komentarze', 'comment_add' => 'Dodaj komentarz', - 'comment_none' => 'No comments to display', + 'comment_none' => 'Brak komentarzy do wyświetlenia', 'comment_placeholder' => 'Napisz swój komentarz tutaj', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', + 'comment_thread_count' => ':count wątek komentarza|:count wątków komentarzy', + 'comment_archived_count' => ':count zarchiwizowanych', + 'comment_archived_threads' => 'Zarchiwizowane wątki', 'comment_save' => 'Zapisz komentarz', 'comment_new' => 'Nowy komentarz', 'comment_created' => 'Skomentowano :createDiff', @@ -410,14 +410,14 @@ 'comment_deleted_success' => 'Komentarz usunięty', 'comment_created_success' => 'Komentarz dodany', 'comment_updated_success' => 'Komentarz zaktualizowany', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', + 'comment_archive_success' => 'Komentarz zarchiwizowany', + 'comment_unarchive_success' => 'Komentarz usunięty z archiwum', + 'comment_view' => 'Zobacz komentarz', + 'comment_jump_to_thread' => 'Przejdź do wątku', 'comment_delete_confirm' => 'Czy na pewno chcesz usunąc ten komentarz?', 'comment_in_reply_to' => 'W odpowiedzi na :commentId', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', + 'comment_reference' => 'Odwołania', + 'comment_reference_outdated' => '(Przestarzałe)', 'comment_editor_explain' => 'Oto komentarze pozostawione na tej stronie. Komentarze mogą być dodawane i zarządzane podczas przeglądania zapisanej strony.', // Revision diff --git a/lang/pl/errors.php b/lang/pl/errors.php index 04146bbb787..244913cac42 100644 --- a/lang/pl/errors.php +++ b/lang/pl/errors.php @@ -106,17 +106,17 @@ 'back_soon' => 'Niedługo zostanie uruchomiona ponownie.', // Import - 'import_zip_cant_read' => 'Could not read ZIP file.', - 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', - 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', - 'import_validation_failed' => 'Import ZIP failed to validate with errors:', - 'import_zip_failed_notification' => 'Failed to import ZIP file.', - 'import_perms_books' => 'You are lacking the required permissions to create books.', - 'import_perms_chapters' => 'You are lacking the required permissions to create chapters.', - 'import_perms_pages' => 'You are lacking the required permissions to create pages.', - 'import_perms_images' => 'You are lacking the required permissions to create images.', - 'import_perms_attachments' => 'You are lacking the required permission to create attachments.', + 'import_zip_cant_read' => 'Nie można odczytać archiwum ZIP.', + 'import_zip_cant_decode_data' => 'Nie udało się odnaleźć i dekodować pliku data.json w zawartości archiwum ZIP.', + 'import_zip_no_data' => 'Dane archiwum ZIP nie zawierają oczekiwanej zawartości książki, rozdziału lub strony.', + 'import_zip_data_too_large' => 'Zawartość pliku data.json w archiwum ZIP przekracza maksymalny dopuszczalny rozmiar narzucony przez aktualną konfigurację aplikacji.', + 'import_validation_failed' => 'Walidacja importu archiwum ZIP nie powiodła się z błędami:', + 'import_zip_failed_notification' => 'Nie udało się zaimportować archiwum ZIP.', + 'import_perms_books' => 'Brakuje Ci wymaganych uprawnień do tworzenia książek.', + 'import_perms_chapters' => 'Brakuje Ci wymaganych uprawnień do tworzenia rozdziałów.', + 'import_perms_pages' => 'Brakuje Ci wymaganych uprawnień do tworzenia stron.', + 'import_perms_images' => 'Brakuje Ci wymaganych uprawnień do tworzenia zdjęć.', + 'import_perms_attachments' => 'Brakuje Ci wymaganych uprawnień do tworzenia załączników.', // API errors 'api_no_authorization_found' => 'Nie znaleziono tokenu autoryzacji dla żądania', @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Podany sekret dla tego API jest nieprawidłowy', 'api_user_no_api_permission' => 'Właściciel używanego tokenu API nie ma uprawnień do wykonywania zapytań do API', 'api_user_token_expired' => 'Token uwierzytelniania wygasł', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Błąd podczas wysyłania testowej wiadomości e-mail:', diff --git a/lang/pl/notifications.php b/lang/pl/notifications.php index a2c9ff1c0b9..937db084fae 100644 --- a/lang/pl/notifications.php +++ b/lang/pl/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => 'Zaktualizowano stronę: :pageName', 'updated_page_intro' => 'Strona została zaktualizowana w :appName:', 'updated_page_debounce' => 'Aby zapobiec nadmiarowi powiadomień, przez jakiś czas nie będziesz otrzymywać powiadomień o dalszych edycjach tej strony przez tego samego edytora.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'Zostałeś oznaczony w komentarzu na stronie: :pageName', + 'comment_mention_intro' => 'Zostałeś oznaczony w komentarzu w :appName:', 'detail_page_name' => 'Nazwa strony:', 'detail_page_path' => 'Ścieżka strony:', diff --git a/lang/pl/preferences.php b/lang/pl/preferences.php index 372b8eda6df..5a308002cca 100644 --- a/lang/pl/preferences.php +++ b/lang/pl/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'Kontroluj otrzymywane powiadomienia e-mail, gdy określona aktywność jest wykonywana w systemie.', 'notifications_opt_own_page_changes' => 'Powiadom o zmianach na stronach, których jestem właścicielem', 'notifications_opt_own_page_comments' => 'Powiadom o komentarzach na stronach, których jestem właścicielem', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'Powiadom, kiedy zostanę oznaczony w komentarzu', 'notifications_opt_comment_replies' => 'Powiadom o odpowiedziach na moje komentarze', 'notifications_save' => 'Zapisz preferencje', 'notifications_update_success' => 'Preferencje powiadomień zostały zaktualizowane!', diff --git a/lang/pl/settings.php b/lang/pl/settings.php index c32797f2f53..5954b005f09 100644 --- a/lang/pl/settings.php +++ b/lang/pl/settings.php @@ -20,12 +20,12 @@ 'app_name_header' => 'Pokaż nazwę aplikacji w nagłówku', 'app_public_access' => 'Dostęp publiczny', 'app_public_access_desc' => 'Włączenie tej opcji umożliwi niezalogowanym odwiedzającym dostęp do treści w Twojej instancji BookStack.', - 'app_public_access_desc_guest' => 'Dostęp dla niezalogowanych odwiedzających jest dostępny poprzez użytkownika "Guest".', + 'app_public_access_desc_guest' => 'Dostęp dla niezalogowanych odwiedzających jest kontrolowany poprzez użytkownika "Guest".', 'app_public_access_toggle' => 'Zezwalaj na dostęp publiczny', 'app_public_viewing' => 'Zezwolić na publiczne przeglądanie?', - 'app_secure_images' => 'Włączyć przesyłanie obrazów o wyższym poziomie bezpieczeństwa?', + 'app_secure_images' => 'Bezpieczniejsze przesyłanie obrazów', 'app_secure_images_toggle' => 'Włącz wyższy poziom bezpieczeństwa dla obrazów', - 'app_secure_images_desc' => 'Ze względów wydajnościowych wszystkie obrazki są publiczne. Ta opcja dodaje dodatkowy, trudny do odgadnięcia losowy ciąg na początku nazwy obrazka. Upewnij się że indeksowanie katalogów jest zablokowane, aby uniemożliwić łatwy dostęp do obrazków.', + 'app_secure_images_desc' => 'Ze względu na wydajność systemu wszystkie obrazki są publiczne. Ta opcja dodaje trudny do odgadnięcia losowy ciąg znaków na początku nazwy obrazka. Upewnij się, że indeksowanie katalogów jest wyłączone, aby uniemożliwić łatwy dostęp do obrazków.', 'app_default_editor' => 'Domyślny edytor stron', 'app_default_editor_desc' => 'Wybierz, który edytor będzie domyślnie używany podczas edycji nowych stron. Może to być nadpisane na poziomie strony, na którym pozwalają na to uprawnienia.', 'app_custom_html' => 'Własna zawartość w tagu ', @@ -64,47 +64,47 @@ 'reg_settings' => 'Ustawienia rejestracji', 'reg_enable' => 'Włącz rejestrację', 'reg_enable_toggle' => 'Włącz rejestrację', - 'reg_enable_desc' => 'Po włączeniu rejestracji użytkownicy ci będą mogli się samodzielnie zarejestrować i otrzymają domyślną rolę.', + 'reg_enable_desc' => 'Przy włączonej rejestracji użytkownicy będą w stanie samodzielnie założyć sobie konto w systemie. Po rejestracji automatycznie otrzymają domyślną rolę.', 'reg_default_role' => 'Domyślna rola użytkownika po rejestracji', 'reg_enable_external_warning' => 'Powyższa opcja jest ignorowana, gdy zewnętrzne uwierzytelnianie LDAP lub SAML jest aktywne. Konta użytkowników dla nieistniejących użytkowników zostaną automatycznie utworzone, jeśli uwierzytelnianie za pomocą systemu zewnętrznego zakończy się sukcesem.', 'reg_email_confirmation' => 'Potwierdzenie adresu email', 'reg_email_confirmation_toggle' => 'Wymagaj potwierdzenia adresu email', - 'reg_confirm_email_desc' => 'Jeśli restrykcje domenowe zostały ustawione, potwierdzenie adresu stanie się konieczne, a poniższa wartośc zostanie zignorowana.', - 'reg_confirm_restrict_domain' => 'Restrykcje domenowe dot. adresu e-mail', - 'reg_confirm_restrict_domain_desc' => 'Wprowadź listę domen adresów e-mail, rozdzieloną przecinkami, którym chciałbyś zezwolić na rejestrację. Wymusi to konieczność potwierdzenia adresu e-mail przez użytkownika przed uzyskaniem dostępu do aplikacji.
    Pamiętaj, że użytkownicy będą mogli zmienić adres e-mail po rejestracji.', + 'reg_confirm_email_desc' => 'Jeśli restrykcje domenowe zostały ustawione, potwierdzenie adresu email stanie się konieczne i ta opcja zostanie zignorowana.', + 'reg_confirm_restrict_domain' => 'Restrykcje domenowe', + 'reg_confirm_restrict_domain_desc' => 'Wprowadź listę domen adresów email, rozdzieloną przecinkami, którym chciałbyś zezwolić na rejestrację. Wymusi to konieczność potwierdzenia adresu e-mail przez użytkownika przed uzyskaniem dostępu do aplikacji.
    Pamiętaj, że użytkownicy będą mogli zmienić adres e-mail po rejestracji.', 'reg_confirm_restrict_domain_placeholder' => 'Brak restrykcji', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', - 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', - 'sorting_rules' => 'Sort Rules', - 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', - 'sort_rule_assigned_to_x_books' => 'Assigned to :count Book|Assigned to :count Books', - 'sort_rule_create' => 'Create Sort Rule', - 'sort_rule_edit' => 'Edit Sort Rule', - 'sort_rule_delete' => 'Delete Sort Rule', - 'sort_rule_delete_desc' => 'Remove this sort rule from the system. Books using this sort will revert to manual sorting.', - 'sort_rule_delete_warn_books' => 'This sort rule is currently used on :count book(s). Are you sure you want to delete this?', - 'sort_rule_delete_warn_default' => 'This sort rule is currently used as the default for books. Are you sure you want to delete this?', - 'sort_rule_details' => 'Sort Rule Details', - 'sort_rule_details_desc' => 'Set a name for this sort rule, which will appear in lists when users are selecting a sort.', - 'sort_rule_operations' => 'Sort Operations', - 'sort_rule_operations_desc' => 'Configure the sort actions to be performed by moving them from the list of available operations. Upon use, the operations will be applied in order, from top to bottom. Any changes made here will be applied to all assigned books upon save.', - 'sort_rule_available_operations' => 'Available Operations', - 'sort_rule_available_operations_empty' => 'No operations remaining', - 'sort_rule_configured_operations' => 'Configured Operations', - 'sort_rule_configured_operations_empty' => 'Drag/add operations from the "Available Operations" list', - 'sort_rule_op_asc' => '(Asc)', - 'sort_rule_op_desc' => '(Desc)', - 'sort_rule_op_name' => 'Name - Alphabetical', - 'sort_rule_op_name_numeric' => 'Name - Numeric', - 'sort_rule_op_created_date' => 'Created Date', - 'sort_rule_op_updated_date' => 'Updated Date', - 'sort_rule_op_chapters_first' => 'Chapters First', - 'sort_rule_op_chapters_last' => 'Chapters Last', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting' => 'Listy i sortowanie', + 'sorting_book_default' => 'Domyślna reguła sortowania książek', + 'sorting_book_default_desc' => 'Wybierz domyślną regułę sortowania dla nowych książek. To nie wpłynie na istniejące książki i może być nadpisane per książka.', + 'sorting_rules' => 'Reguły sortowania', + 'sorting_rules_desc' => 'Są to wstępnie zdefiniowane operacje sortowania, które mogą być stosowane do treści w systemie.', + 'sort_rule_assigned_to_x_books' => 'Przypisane do :count książki|Przypisane do :count książek', + 'sort_rule_create' => 'Utwórz regułę sortowania', + 'sort_rule_edit' => 'Edytuj regułę sortowania', + 'sort_rule_delete' => 'Usuń regułę sortowania', + 'sort_rule_delete_desc' => 'Usuń tę regułę sortowania z systemu. Książki używające tej reguły powrócą do ręcznego sortowania.', + 'sort_rule_delete_warn_books' => 'Ta reguła sortowania jest obecnie używana na :count książkach. Czy na pewno chcesz ją usunąć?', + 'sort_rule_delete_warn_default' => 'Ta reguła sortowania jest obecnie używana jako domyślna dla książek. Czy na pewno chcesz ją usunąć?', + 'sort_rule_details' => 'Szczegóły reguły sortowania', + 'sort_rule_details_desc' => 'Ustaw nazwę dla tej reguły sortowania, która pojawi się na listach, gdy użytkownicy skorzystają z sortowania.', + 'sort_rule_operations' => 'Operacje sortowania', + 'sort_rule_operations_desc' => 'Skonfiguruj akcje sortowanie do wykonania, przenosząc je z listy dostępnych operacji. Po użyciu operacje zostaną zastosowane w kolejności od góry do dołu. Wszelkie zmiany wprowadzone tutaj zostaną zastosowane do wszystkich przypisanych książek po zapisaniu.', + 'sort_rule_available_operations' => 'Dostępne operacje', + 'sort_rule_available_operations_empty' => 'Brak pozostałych operacji', + 'sort_rule_configured_operations' => 'Skonfigurowane operacje', + 'sort_rule_configured_operations_empty' => 'Przeciągnij/dodaj operacje z listy "Dostępne Operacje"', + 'sort_rule_op_asc' => '(rosnąco)', + 'sort_rule_op_desc' => '(malejąco)', + 'sort_rule_op_name' => 'Nazwa — alfabetycznie', + 'sort_rule_op_name_numeric' => 'Nazwa — numerycznie', + 'sort_rule_op_created_date' => 'Data utworzenia', + 'sort_rule_op_updated_date' => 'Data aktualizacji', + 'sort_rule_op_chapters_first' => 'Rozdziały na początku', + 'sort_rule_op_chapters_last' => 'Rozdziały na końcu', + 'sorting_page_limits' => 'Limity wyświetlania per strona', + 'sorting_page_limits_desc' => 'Ustaw ile elementów pokazywać per strona w różnych listach w systemie. Zazwyczaj mniejsza ilość będzie bardziej wydajna, podczas gdy większa ilość unika konieczności przeglądania wielu stron. Zaleca się stosowanie parzystej wielokrotności 3 (18, 24, 30 itp...).', // Maintenance settings 'maint' => 'Konserwacja', @@ -194,16 +194,16 @@ 'role_access_api' => 'Dostęp do systemowego API', 'role_manage_settings' => 'Zarządzanie ustawieniami aplikacji', 'role_export_content' => 'Eksportuj zawartość', - 'role_import_content' => 'Import content', + 'role_import_content' => 'Importuj zawartość', 'role_editor_change' => 'Zmień edytor strony', 'role_notifications' => 'Odbieranie i zarządzanie powiadomieniami', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => 'Uprawnienia te mogą zapewnić również widoczność i wyszukiwanie użytkowników i ról w systemie.', 'role_asset' => 'Zarządzanie zasobami', 'roles_system_warning' => 'Pamiętaj, że dostęp do trzech powyższych uprawnień może pozwolić użytkownikowi na zmianę własnych uprawnień lub uprawnień innych osób w systemie. Przypisz tylko role z tymi uprawnieniami do zaufanych użytkowników.', 'role_asset_desc' => 'Te ustawienia kontrolują zarządzanie zasobami systemu. Uprawnienia książek, rozdziałów i stron nadpisują te ustawienia.', 'role_asset_admins' => 'Administratorzy mają automatycznie dostęp do wszystkich treści, ale te opcję mogą być pokazywać lub ukrywać opcje interfejsu użytkownika.', 'role_asset_image_view_note' => 'To odnosi się do widoczności w ramach menedżera obrazów. Rzeczywista możliwość dostępu do przesłanych plików obrazów będzie zależeć od systemowej opcji przechowywania obrazów.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Uprawnienia te mogą zapewnić również widoczność i wyszukiwanie użytkowników w systemie.', 'role_all' => 'Wszyscy', 'role_own' => 'Własne', 'role_controlled_by_asset' => 'Kontrolowane przez zasób, do którego zostały udostępnione', diff --git a/lang/pl/validation.php b/lang/pl/validation.php index d1e8fada1df..989d5b17a58 100644 --- a/lang/pl/validation.php +++ b/lang/pl/validation.php @@ -105,11 +105,11 @@ 'url' => 'Format :attribute jest nieprawidłowy.', 'uploaded' => 'Plik nie może zostać wysłany. Serwer nie akceptuje plików o takim rozmiarze.', - 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', - 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', - 'zip_model_expected' => 'Data object expected but ":type" found.', - 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', + 'zip_file' => ':attribute musi odnosić się do pliku w archiwum ZIP.', + 'zip_file_size' => 'Plik :attribute nie może przekraczać :size MB.', + 'zip_file_mime' => ':attribute musi odnosić się do pliku typu :validTypes. Znaleziono :foundType.', + 'zip_model_expected' => 'Oczekiwano obiektu danych, ale znaleziono ":type".', + 'zip_unique' => ':attribute musi być unikalny dla typu obiektu w archiwum ZIP.', // Custom validation lines 'custom' => [ diff --git a/lang/pt/errors.php b/lang/pt/errors.php index 973bf61e2dc..522d7f4c81a 100644 --- a/lang/pt/errors.php +++ b/lang/pt/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'O segredo fornecido para o token de API usado está incorreto', 'api_user_no_api_permission' => 'O proprietário do token de API utilizado não tem permissão para fazer requisições de API', 'api_user_token_expired' => 'O token de autenticação expirou', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Erro lançado ao enviar um e-mail de teste:', diff --git a/lang/pt_BR/errors.php b/lang/pt_BR/errors.php index 37cbd7ff2c4..4d711076f46 100644 --- a/lang/pt_BR/errors.php +++ b/lang/pt_BR/errors.php @@ -126,6 +126,7 @@ 'api_incorrect_token_secret' => 'O segredo fornecido para o código de API usado está incorreto', 'api_user_no_api_permission' => 'O proprietário do código de API utilizado não tem permissão para fazer requisições de API', 'api_user_token_expired' => 'O código de autenticação expirou', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Erro encontrado ao enviar uma mensagem eletrônica de teste:', diff --git a/lang/ro/errors.php b/lang/ro/errors.php index dec7be134ad..550c9c5d588 100644 --- a/lang/ro/errors.php +++ b/lang/ro/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Secretul furnizat pentru token-ul API folosit este incorect', 'api_user_no_api_permission' => 'Proprietarul token-ului API folosit nu are permisiunea de a efectua apeluri API', 'api_user_token_expired' => 'Token-ul de autorizare utilizat a expirat', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Eroare la trimiterea unui e-mail de test:', diff --git a/lang/ru/errors.php b/lang/ru/errors.php index 82aecb657ae..aba67adf084 100644 --- a/lang/ru/errors.php +++ b/lang/ru/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Секрет, предоставленный для данного использованного API токена неверен', 'api_user_no_api_permission' => 'Владелец используемого API токена не имеет прав на выполнение вызовов API', 'api_user_token_expired' => 'Срок действия используемого токена авторизации истек', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Ошибка при отправке тестового письма:', diff --git a/lang/sk/errors.php b/lang/sk/errors.php index 30609b20146..bfd921d0683 100644 --- a/lang/sk/errors.php +++ b/lang/sk/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Secret poskytnutý pre daný token API je nesprávny', 'api_user_no_api_permission' => 'Vlastník použitého tokenu API nemá povolenie na uskutočňovanie volaní rozhrania API', 'api_user_token_expired' => 'Platnosť použitého autorizačného tokenu vypršala', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Chyba pri odosielaní testovacieho e-mailu:', diff --git a/lang/sl/errors.php b/lang/sl/errors.php index cb3db274712..5d018bcbef0 100644 --- a/lang/sl/errors.php +++ b/lang/sl/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Skrivnost, ki je bila dana za uporabljeni žeton API, je napačna', 'api_user_no_api_permission' => 'Lastnik API nima pravic za klicanje API', 'api_user_token_expired' => 'Avtorizacijski žeton je pretečen', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Napaka se je pojavila pri pošiljanju testne e-pošte:', diff --git a/lang/sq/errors.php b/lang/sq/errors.php index 77d7ee69e49..20537d59f0c 100644 --- a/lang/sq/errors.php +++ b/lang/sq/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'The secret provided for the given used API token is incorrect', 'api_user_no_api_permission' => 'The owner of the used API token does not have permission to make API calls', 'api_user_token_expired' => 'The authorization token used has expired', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', diff --git a/lang/sr/errors.php b/lang/sr/errors.php index f28fc01a94e..55ba90a5c7a 100644 --- a/lang/sr/errors.php +++ b/lang/sr/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'The secret provided for the given used API token is incorrect', 'api_user_no_api_permission' => 'The owner of the used API token does not have permission to make API calls', 'api_user_token_expired' => 'The authorization token used has expired', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', diff --git a/lang/sv/errors.php b/lang/sv/errors.php index 3a5478977c9..0548f96fd25 100644 --- a/lang/sv/errors.php +++ b/lang/sv/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Hemligheten för den angivna API-token är felaktig', 'api_user_no_api_permission' => 'Ägaren av den använda API-token har inte behörighet att göra API-anrop', 'api_user_token_expired' => 'Den använda auktoriseringstoken har löpt ut', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Ett fel uppstod när ett test mail skulle skickas:', diff --git a/lang/tk/errors.php b/lang/tk/errors.php index 77d7ee69e49..20537d59f0c 100644 --- a/lang/tk/errors.php +++ b/lang/tk/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'The secret provided for the given used API token is incorrect', 'api_user_no_api_permission' => 'The owner of the used API token does not have permission to make API calls', 'api_user_token_expired' => 'The authorization token used has expired', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', diff --git a/lang/tr/errors.php b/lang/tr/errors.php index 259cc4b1c55..66d836867c0 100644 --- a/lang/tr/errors.php +++ b/lang/tr/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Kullanılan API için sağlanan gizli anahtar doğru değil', 'api_user_no_api_permission' => 'Kullanılan API anahtarının sahibi API çağrısı yapmak için izne sahip değil', 'api_user_token_expired' => 'Kullanılan yetkilendirme anahtarının süresi doldu', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Test e-postası gönderilirken bir hata meydana geldi:', diff --git a/lang/uk/errors.php b/lang/uk/errors.php index 2d32276a399..e7632359310 100644 --- a/lang/uk/errors.php +++ b/lang/uk/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Секрет, наданий для даного використовуваного токена API є неправильним', 'api_user_no_api_permission' => 'Власник використовуваного токена API не має дозволу здійснювати виклики API', 'api_user_token_expired' => 'Термін дії токена авторизації закінчився', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Помилка під час надсилання тестового електронного листа:', diff --git a/lang/uz/errors.php b/lang/uz/errors.php index 052b29adf9a..50593eae6bf 100644 --- a/lang/uz/errors.php +++ b/lang/uz/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Foydalanilgan API tokeni uchun berilgan sir notoʻgʻri', 'api_user_no_api_permission' => 'Foydalanilgan API tokeni egasi API qoʻngʻiroqlarini amalga oshirishga ruxsatga ega emas', 'api_user_token_expired' => 'Amaldagi avtorizatsiya tokeni muddati tugagan', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Sinov xatini yuborishda xatolik yuz berdi:', diff --git a/lang/vi/errors.php b/lang/vi/errors.php index d422b716a0f..18980c17ea2 100644 --- a/lang/vi/errors.php +++ b/lang/vi/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => 'Mã bí mật được cung cấp cho token API đang được sử dụng không hợp lệ', 'api_user_no_api_permission' => 'Chủ của token API đang sử dụng không có quyền gọi API', 'api_user_token_expired' => 'Token sử dụng cho việc ủy quyền đã hết hạn', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => 'Lỗi khi gửi email thử:', diff --git a/lang/zh_CN/errors.php b/lang/zh_CN/errors.php index dfcd3d61a73..74814c6b0c0 100644 --- a/lang/zh_CN/errors.php +++ b/lang/zh_CN/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => '给已给出的API所提供的密钥不正确', 'api_user_no_api_permission' => '使用过的 API 令牌的所有者没有进行API 调用的权限', 'api_user_token_expired' => '所使用的身份令牌已过期', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => '发送测试电子邮件时出现错误:', diff --git a/lang/zh_TW/errors.php b/lang/zh_TW/errors.php index f9511dcb068..e5c08ca141b 100644 --- a/lang/zh_TW/errors.php +++ b/lang/zh_TW/errors.php @@ -125,6 +125,7 @@ 'api_incorrect_token_secret' => '給定使用的 API 權杖的密碼錯誤', 'api_user_no_api_permission' => '使用的 API 權杖擁有者無權呼叫 API', 'api_user_token_expired' => '使用的授權權杖已過期', + 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', // Settings & Maintenance 'maintenance_test_email_failure' => '寄送測試電子郵件時發生錯誤:', From 46dcc30bf7504459385192a3edcf525a4877a4de Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 29 Jan 2026 15:18:06 +0000 Subject: [PATCH 032/204] Updated translator & dependency attribution before release v25.12.3 --- .github/translators.txt | 2 ++ dev/licensing/php-library-licenses.txt | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/translators.txt b/.github/translators.txt index 461e89d1666..14c51bd0b32 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -528,3 +528,5 @@ serinf-lauza :: French Diyan Nikolaev (nikolaev.diyan) :: Bulgarian Shadluk Avan (quldosh) :: Uzbek Marci (MartonPoto) :: Hungarian +Michał Sadurski (wheeskeey) :: Polish +JanDziaslo :: Polish diff --git a/dev/licensing/php-library-licenses.txt b/dev/licensing/php-library-licenses.txt index d0cc347d910..abbbf826daf 100644 --- a/dev/licensing/php-library-licenses.txt +++ b/dev/licensing/php-library-licenses.txt @@ -237,21 +237,21 @@ Link: https://config.thephpleague.com league/flysystem License: MIT License File: vendor/league/flysystem/LICENSE -Copyright: Copyright (c) 2013-2024 Frank de Jonge +Copyright: Copyright (c) 2013-2026 Frank de Jonge Source: https://github.com/thephpleague/flysystem.git Link: https://github.com/thephpleague/flysystem.git ----------- league/flysystem-aws-s3-v3 License: MIT License File: vendor/league/flysystem-aws-s3-v3/LICENSE -Copyright: Copyright (c) 2013-2024 Frank de Jonge +Copyright: Copyright (c) 2013-2026 Frank de Jonge Source: https://github.com/thephpleague/flysystem-aws-s3-v3.git Link: https://github.com/thephpleague/flysystem-aws-s3-v3.git ----------- league/flysystem-local License: MIT License File: vendor/league/flysystem-local/LICENSE -Copyright: Copyright (c) 2013-2024 Frank de Jonge +Copyright: Copyright (c) 2013-2026 Frank de Jonge Source: https://github.com/thephpleague/flysystem-local.git Link: https://github.com/thephpleague/flysystem-local.git ----------- @@ -323,7 +323,7 @@ License: MIT License File: vendor/nesbot/carbon/LICENSE Copyright: Copyright (C) Brian Nesbitt Source: https://github.com/CarbonPHP/carbon.git -Link: https://carbon.nesbot.com +Link: https://carbonphp.github.io/carbon/ ----------- nette/schema License: BSD-3-Clause GPL-2.0-only GPL-3.0-only From 4949520194a8397497e15155e90c46de46693bde Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 1 Feb 2026 11:53:46 +0000 Subject: [PATCH 033/204] Theme System: Added initial module implementations --- app/App/Providers/ThemeServiceProvider.php | 3 +- app/Theming/ThemeController.php | 7 +- app/Theming/ThemeModule.php | 50 ++++++++++++ app/Theming/ThemeService.php | 93 ++++++++++++++++++++-- app/Theming/ThemeViews.php | 10 ++- app/Translation/FileLoader.php | 19 +++-- app/Util/SvgIcon.php | 11 ++- 7 files changed, 168 insertions(+), 25 deletions(-) create mode 100644 app/Theming/ThemeModule.php diff --git a/app/App/Providers/ThemeServiceProvider.php b/app/App/Providers/ThemeServiceProvider.php index e32f90b9afe..50c76bbf846 100644 --- a/app/App/Providers/ThemeServiceProvider.php +++ b/app/App/Providers/ThemeServiceProvider.php @@ -31,12 +31,13 @@ public function boot(): void return; } + $themeService->loadModules(); $themeService->readThemeActions(); $themeService->dispatch(ThemeEvents::APP_BOOT, $this->app); $themeViews = new ThemeViews(); $themeService->dispatch(ThemeEvents::THEME_REGISTER_VIEWS, $themeViews); - $themeViews->registerViewPathsForTheme($viewFactory->getFinder()); + $themeViews->registerViewPathsForTheme($viewFactory->getFinder(), $themeService->getModules()); if ($themeViews->hasRegisteredViews()) { $viewFactory->share('__themeViews', $themeViews); Blade::directive('include', function ($expression) { diff --git a/app/Theming/ThemeController.php b/app/Theming/ThemeController.php index 1eecc697428..c2676780371 100644 --- a/app/Theming/ThemeController.php +++ b/app/Theming/ThemeController.php @@ -5,21 +5,22 @@ use BookStack\Facades\Theme; use BookStack\Http\Controller; use BookStack\Util\FilePathNormalizer; +use Symfony\Component\HttpFoundation\StreamedResponse; class ThemeController extends Controller { /** * Serve a public file from the configured theme. */ - public function publicFile(string $theme, string $path) + public function publicFile(string $theme, string $path): StreamedResponse { $cleanPath = FilePathNormalizer::normalize($path); if ($theme !== Theme::getTheme() || !$cleanPath) { abort(404); } - $filePath = theme_path("public/{$cleanPath}"); - if (!file_exists($filePath)) { + $filePath = Theme::findFirstFile("public/{$cleanPath}"); + if (!$filePath) { abort(404); } diff --git a/app/Theming/ThemeModule.php b/app/Theming/ThemeModule.php new file mode 100644 index 00000000000..9bbc0103ae5 --- /dev/null +++ b/app/Theming/ThemeModule.php @@ -0,0 +1,50 @@ +name = $data['name']; + $module->description = $data['description']; + $module->folderName = $folderName; + $module->version = $data['version']; + + return $module; + } + + /** + * Get a path for a file within this module. + */ + public function path($path = ''): string + { + $component = trim($path, '/'); + return theme_path("modules/{$this->folderName}/{$component}"); + } +} diff --git a/app/Theming/ThemeService.php b/app/Theming/ThemeService.php index 14281adca30..6f31129804c 100644 --- a/app/Theming/ThemeService.php +++ b/app/Theming/ThemeService.php @@ -16,6 +16,11 @@ class ThemeService */ protected array $listeners = []; + /** + * @var array + */ + protected array $modules = []; + /** * Get the currently configured theme. * Returns an empty string if not configured. @@ -77,20 +82,94 @@ public function registerCommand(Command $command): void } /** - * Read any actions from the set theme path if the 'functions.php' file exists. + * Read any actions from the 'functions.php' file of the active theme or its modules. */ public function readThemeActions(): void { - $themeActionsFile = theme_path('functions.php'); - if (!$themeActionsFile || !file_exists($themeActionsFile)) { + $moduleFunctionFiles = array_map(function (ThemeModule $module): string { + return $module->path('functions.php'); + }, $this->modules); + $allFunctionFiles = array_merge(array_values($moduleFunctionFiles), [theme_path('functions.php')]); + $filteredFunctionFiles = array_filter($allFunctionFiles, function (string $file): bool { + return $file && file_exists($file); + }); + + foreach ($filteredFunctionFiles as $functionFile) { + try { + require $functionFile; + } catch (\Error $exception) { + throw new ThemeException("Failed loading theme functions file at \"{$functionFile}\" with error: {$exception->getMessage()}"); + } + } + } + + /** + * Read the modules folder and load in any valid theme modules. + */ + public function loadModules(): void + { + $modulesFolder = theme_path('modules'); + if (!$modulesFolder || !is_dir($modulesFolder)) { return; } - try { - require $themeActionsFile; - } catch (\Error $exception) { - throw new ThemeException("Failed loading theme functions file at \"{$themeActionsFile}\" with error: {$exception->getMessage()}"); + $subFolders = array_filter(scandir($modulesFolder), function ($item) use ($modulesFolder) { + return $item !== '.' && $item !== '..' && is_dir($modulesFolder . DIRECTORY_SEPARATOR . $item); + }); + + foreach ($subFolders as $folderName) { + $moduleJsonFile = $modulesFolder . DIRECTORY_SEPARATOR . $folderName . DIRECTORY_SEPARATOR . 'bookstack-module.json'; + + if (!file_exists($moduleJsonFile)) { + continue; + } + + try { + $jsonContent = file_get_contents($moduleJsonFile); + $jsonData = json_decode($jsonContent, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new ThemeException("Invalid JSON in module file at \"{$moduleJsonFile}\": " . json_last_error_msg()); + } + + $module = ThemeModule::fromJson($jsonData, $folderName); + $this->modules[$folderName] = $module; + } catch (ThemeException $exception) { + throw $exception; + } catch (\Exception $exception) { + throw new ThemeException("Failed loading module from \"{$moduleJsonFile}\" with error: {$exception->getMessage()}"); + } + } + } + + /** + * Get all loaded theme modules. + * @return array + */ + public function getModules(): array + { + return $this->modules; + } + + /** + * Look for a specific file within the theme or its modules. + * Returns the first file found or null if not found. + */ + public function findFirstFile(string $path): ?string + { + $themePath = theme_path($path); + if (file_exists($themePath)) { + return $themePath; + } + + foreach ($this->modules as $module) { + $customizedFile = $module->path($path); + if (file_exists($customizedFile)) { + return $customizedFile; + } } + + return null; } /** diff --git a/app/Theming/ThemeViews.php b/app/Theming/ThemeViews.php index 719f8e3ce24..b2d0adc02f6 100644 --- a/app/Theming/ThemeViews.php +++ b/app/Theming/ThemeViews.php @@ -20,9 +20,17 @@ class ThemeViews /** * Register any extra paths for where we may expect views to be located * with the provided FileViewFinder, to make custom views available for use. + * @param ThemeModule[] $modules */ - public function registerViewPathsForTheme(FileViewFinder $finder): void + public function registerViewPathsForTheme(FileViewFinder $finder, array $modules): void { + foreach ($modules as $module) { + $moduleViewsPath = $module->path('views'); + if (file_exists($moduleViewsPath) && is_dir($moduleViewsPath)) { + $finder->prependLocation($moduleViewsPath); + } + } + $finder->prependLocation(theme_path()); } diff --git a/app/Translation/FileLoader.php b/app/Translation/FileLoader.php index 1fec4d18bb1..6212506ddf6 100644 --- a/app/Translation/FileLoader.php +++ b/app/Translation/FileLoader.php @@ -2,6 +2,7 @@ namespace BookStack\Translation; +use BookStack\Facades\Theme; use Illuminate\Translation\FileLoader as BaseLoader; class FileLoader extends BaseLoader @@ -12,11 +13,6 @@ class FileLoader extends BaseLoader * Extends Laravel's translation FileLoader to look in multiple directories * so that we can load in translation overrides from the theme file if wanted. * - * Note: As of using Laravel 10, this may now be redundant since Laravel's - * file loader supports multiple paths. This needs further testing though - * to confirm if Laravel works how we expect, since we specifically need - * the theme folder to be able to partially override core lang files. - * * @param string $locale * @param string $group * @param string|null $namespace @@ -32,9 +28,18 @@ public function load($locale, $group, $namespace = null): array if (is_null($namespace) || $namespace === '*') { $themePath = theme_path('lang'); $themeTranslations = $themePath ? $this->loadPaths([$themePath], $locale, $group) : []; - $originalTranslations = $this->loadPaths($this->paths, $locale, $group); - return array_merge($originalTranslations, $themeTranslations); + $modules = Theme::getModules(); + $moduleTranslations = []; + foreach ($modules as $module) { + $modulePath = $module->path('lang'); + if (file_exists($modulePath)) { + $moduleTranslations = array_merge($moduleTranslations, $this->loadPaths([$modulePath], $locale, $group)); + } + } + + $originalTranslations = $this->loadPaths($this->paths, $locale, $group); + return array_merge($originalTranslations, $moduleTranslations, $themeTranslations); } return $this->loadNamespaced($locale, $group, $namespace); diff --git a/app/Util/SvgIcon.php b/app/Util/SvgIcon.php index ce6e1c23e37..b1b14a4872a 100644 --- a/app/Util/SvgIcon.php +++ b/app/Util/SvgIcon.php @@ -2,6 +2,8 @@ namespace BookStack\Util; +use BookStack\Facades\Theme; + class SvgIcon { public function __construct( @@ -23,12 +25,9 @@ public function toHtml(): string $attrString .= $attrName . '="' . $attr . '" '; } - $iconPath = resource_path('icons/' . $this->name . '.svg'); - $themeIconPath = theme_path('icons/' . $this->name . '.svg'); - - if ($themeIconPath && file_exists($themeIconPath)) { - $iconPath = $themeIconPath; - } elseif (!file_exists($iconPath)) { + $defaultIconPath = resource_path('icons/' . $this->name . '.svg'); + $iconPath = Theme::findFirstFile("icons/{$this->name}.svg") ?? $defaultIconPath; + if (!file_exists($iconPath)) { return ''; } From cd84074cdf80242fe177fe8b16401bb14e46a7ce Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 1 Feb 2026 16:27:52 +0000 Subject: [PATCH 034/204] Theme System: Split & organised tests, changed module version to string --- app/Theming/ThemeModule.php | 8 +- tests/TestCase.php | 18 ++ .../LogicalThemeEventsTest.php} | 273 ++---------------- tests/Theme/LogicalThemeTest.php | 105 +++++++ tests/Theme/VisualThemeTest.php | 132 +++++++++ 5 files changed, 278 insertions(+), 258 deletions(-) rename tests/{ThemeTest.php => Theme/LogicalThemeEventsTest.php} (50%) create mode 100644 tests/Theme/LogicalThemeTest.php create mode 100644 tests/Theme/VisualThemeTest.php diff --git a/app/Theming/ThemeModule.php b/app/Theming/ThemeModule.php index 9bbc0103ae5..f873ed247bd 100644 --- a/app/Theming/ThemeModule.php +++ b/app/Theming/ThemeModule.php @@ -9,7 +9,7 @@ class ThemeModule protected string $name; protected string $description; protected string $folderName; - protected int $version; + protected string $version; /** * Create a ThemeModule instance from JSON data. @@ -26,10 +26,14 @@ public static function fromJson(array $data, string $folderName): static throw new ThemeException("Module in folder \"{$folderName}\" is missing a valid 'description' property"); } - if (!isset($data['version']) || !is_int($data['version']) || $data['version'] < 1) { + if (!isset($data['version']) || !is_string($data['version'])) { throw new ThemeException("Module in folder \"{$folderName}\" is missing a valid 'version' property"); } + if (!preg_match('/^v?\d+\.\d+\.\d+(-.*)?$/', $data['version'])) { + throw new ThemeException("Module in folder \"{$folderName}\" has an invalid 'version' format. Expected semantic version format like '1.0.0' or 'v1.0.0'"); + } + $module = new static(); $module->name = $data['name']; $module->description = $data['description']; diff --git a/tests/TestCase.php b/tests/TestCase.php index f69f20d4c55..c6f811b3158 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -13,6 +13,7 @@ use Illuminate\Http\JsonResponse; use Illuminate\Support\Env; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Log; use Illuminate\Testing\Assert as PHPUnit; use Illuminate\Testing\Constraints\HasInDatabase; @@ -157,6 +158,23 @@ protected function runWithEnv(array $valuesByKey, callable $callback, bool $hand } } + protected function usingThemeFolder(callable $callback): void + { + // Create a folder and configure a theme + $themeFolderName = 'testing_theme_' . str_shuffle(rtrim(base64_encode(time()), '=')); + config()->set('view.theme', $themeFolderName); + $themeFolderPath = theme_path(''); + + // Create a theme folder and clean it up on application tear-down + File::makeDirectory($themeFolderPath); + $this->beforeApplicationDestroyed(fn() => File::deleteDirectory($themeFolderPath)); + + // Run provided callback with the theme env option set + $this->runWithEnv(['APP_THEME' => $themeFolderName], function () use ($callback, $themeFolderName) { + call_user_func($callback, $themeFolderName); + }); + } + /** * Check the keys and properties in the given map to include * exist, albeit not exclusively, within the map to check. diff --git a/tests/ThemeTest.php b/tests/Theme/LogicalThemeEventsTest.php similarity index 50% rename from tests/ThemeTest.php rename to tests/Theme/LogicalThemeEventsTest.php index f640513cf1d..0a4afd2f4e3 100644 --- a/tests/ThemeTest.php +++ b/tests/Theme/LogicalThemeEventsTest.php @@ -1,6 +1,6 @@ usingThemeFolder(function () { - $translationPath = theme_path('/lang/en'); - File::makeDirectory($translationPath, 0777, true); - - $customTranslations = ' \'Sandwiches\']; - '; - file_put_contents($translationPath . '/entities.php', $customTranslations); - - $homeRequest = $this->actingAs($this->users->viewer())->get('/'); - $this->withHtml($homeRequest)->assertElementContains('header nav', 'Sandwiches'); - }); - } - - public function test_theme_functions_file_used_and_app_boot_event_runs() - { - $this->usingThemeFolder(function ($themeFolder) { - $functionsFile = theme_path('functions.php'); - app()->alias('cat', 'dog'); - file_put_contents($functionsFile, "alias('cat', 'dog');});"); - $this->runWithEnv(['APP_THEME' => $themeFolder], function () { - $this->assertEquals('cat', $this->app->getAlias('dog')); - }); - }); - } - - public function test_theme_functions_loads_errors_are_caught_and_logged() - { - $this->usingThemeFolder(function ($themeFolder) { - $functionsFile = theme_path('functions.php'); - file_put_contents($functionsFile, "expectException(ThemeException::class); - $this->expectExceptionMessageMatches('/Failed loading theme functions file at ".*?" with error: Class "BookStack\\\\Biscuits" not found/'); - - $this->runWithEnv(['APP_THEME' => $themeFolder], fn() => null); - }); - } - - public function test_event_commonmark_environment_configure() + public function test_commonmark_environment_configure() { $callbackCalled = false; $callback = function ($environment) use (&$callbackCalled) { @@ -83,7 +36,7 @@ public function test_event_commonmark_environment_configure() $this->assertTrue($callbackCalled); } - public function test_event_web_middleware_before() + public function test_web_middleware_before() { $callbackCalled = false; $requestParam = null; @@ -100,7 +53,7 @@ public function test_event_web_middleware_before() $this->assertEquals('cat', $requestParam->header('donkey')); } - public function test_event_web_middleware_before_return_val_used_as_response() + public function test_web_middleware_before_return_val_used_as_response() { $callback = function (Request $request) { return response('cat', 412); @@ -112,7 +65,7 @@ public function test_event_web_middleware_before_return_val_used_as_response() $resp->assertStatus(412); } - public function test_event_web_middleware_after() + public function test_web_middleware_after() { $callbackCalled = false; $requestParam = null; @@ -133,7 +86,7 @@ public function test_event_web_middleware_after() $resp->assertHeader('donkey', 'cat123'); } - public function test_event_web_middleware_after_return_val_used_as_response() + public function test_web_middleware_after_return_val_used_as_response() { $callback = function () { return response('cat456', 443); @@ -146,7 +99,7 @@ public function test_event_web_middleware_after_return_val_used_as_response() $resp->assertStatus(443); } - public function test_event_auth_login_standard() + public function test_auth_login_standard() { $args = []; $callback = function (...$eventArgs) use (&$args) { @@ -161,7 +114,7 @@ public function test_event_auth_login_standard() $this->assertInstanceOf(User::class, $args[1]); } - public function test_event_auth_register_standard() + public function test_auth_register_standard() { $args = []; $callback = function (...$eventArgs) use (&$args) { @@ -178,7 +131,7 @@ public function test_event_auth_register_standard() $this->assertInstanceOf(User::class, $args[1]); } - public function test_event_auth_pre_register() + public function test_auth_pre_register() { $args = []; $callback = function (...$eventArgs) use (&$args) { @@ -200,7 +153,7 @@ public function test_event_auth_pre_register() $this->assertDatabaseHas('users', ['email' => $user->email]); } - public function test_event_auth_pre_register_with_false_return_blocks_registration() + public function test_auth_pre_register_with_false_return_blocks_registration() { $callback = function () { return false; @@ -215,7 +168,7 @@ public function test_event_auth_pre_register_with_false_return_blocks_registrati $this->assertDatabaseMissing('users', ['email' => $user->email]); } - public function test_event_webhook_call_before() + public function test_webhook_call_before() { $args = []; $callback = function (...$eventArgs) use (&$args) { @@ -245,7 +198,7 @@ public function test_event_webhook_call_before() $this->assertEquals('hello!', $reqData['test']); } - public function test_event_activity_logged() + public function test_activity_logged() { $book = $this->entities->book(); $args = []; @@ -262,7 +215,7 @@ public function test_event_activity_logged() $this->assertEquals($book->id, $args[1]->id); } - public function test_event_page_include_parse() + public function test_page_include_parse() { /** @var Page $page */ /** @var Page $otherPage */ @@ -293,7 +246,7 @@ public function test_event_page_include_parse() $this->assertEquals($otherPage->id, $args[3]->id); } - public function test_event_routes_register_web_and_web_auth() + public function test_routes_register_web_and_web_auth() { $functionsContent = <<<'END' 'abc123', - 'client_secret' => 'def456', - ], 'SocialiteProviders\Discord\DiscordExtendSocialite@handleTesting'); - - $this->assertEquals('catnet', config('services.catnet.name')); - $this->assertEquals('abc123', config('services.catnet.client_id')); - $this->assertEquals(url('/login/service/catnet/callback'), config('services.catnet.redirect')); - - $loginResp = $this->get('/login'); - $loginResp->assertSee('login/service/catnet'); - } - - public function test_add_social_driver_uses_name_in_config_if_given() - { - Theme::addSocialDriver('catnet', [ - 'client_id' => 'abc123', - 'client_secret' => 'def456', - 'name' => 'Super Cat Name', - ], 'SocialiteProviders\Discord\DiscordExtendSocialite@handleTesting'); - - $this->assertEquals('Super Cat Name', config('services.catnet.name')); - $loginResp = $this->get('/login'); - $loginResp->assertSee('Super Cat Name'); - } - - public function test_add_social_driver_allows_a_configure_for_redirect_callback_to_be_passed() - { - Theme::addSocialDriver( - 'discord', - [ - 'client_id' => 'abc123', - 'client_secret' => 'def456', - 'name' => 'Super Cat Name', - ], - 'SocialiteProviders\Discord\DiscordExtendSocialite@handle', - function ($driver) { - $driver->with(['donkey' => 'donut']); - } - ); - - $loginResp = $this->get('/login/service/discord'); - $redirect = $loginResp->headers->get('location'); - $this->assertStringContainsString('donkey=donut', $redirect); - } - - public function test_register_command_allows_provided_command_to_be_usable_via_artisan() - { - Theme::registerCommand(new MyCustomCommand()); - - Artisan::call('bookstack:test-custom-command', []); - $output = Artisan::output(); - - $this->assertStringContainsString('Command ran!', $output); - } - - public function test_base_body_start_and_end_template_files_can_be_used() - { - $bodyStartStr = 'barry-fought-against-the-panther'; - $bodyEndStr = 'barry-lost-his-fight-with-grace'; - - $this->usingThemeFolder(function (string $folder) use ($bodyStartStr, $bodyEndStr) { - $viewDir = theme_path('layouts/parts'); - mkdir($viewDir, 0777, true); - file_put_contents($viewDir . '/base-body-start.blade.php', $bodyStartStr); - file_put_contents($viewDir . '/base-body-end.blade.php', $bodyEndStr); - - $resp = $this->asEditor()->get('/'); - $resp->assertSee($bodyStartStr); - $resp->assertSee($bodyEndStr); - }); - } - - public function test_export_body_start_and_end_template_files_can_be_used() - { - $bodyStartStr = 'garry-fought-against-the-panther'; - $bodyEndStr = 'garry-lost-his-fight-with-grace'; - $page = $this->entities->page(); - - $this->usingThemeFolder(function (string $folder) use ($bodyStartStr, $bodyEndStr, $page) { - $viewDir = theme_path('layouts/parts'); - mkdir($viewDir, 0777, true); - file_put_contents($viewDir . '/export-body-start.blade.php', $bodyStartStr); - file_put_contents($viewDir . '/export-body-end.blade.php', $bodyEndStr); - - $resp = $this->asEditor()->get($page->getUrl('/export/html')); - $resp->assertSee($bodyStartStr); - $resp->assertSee($bodyEndStr); - }); - } - - public function test_login_and_register_message_template_files_can_be_used() - { - $loginMessage = 'Welcome to this instance, login below you scallywag'; - $registerMessage = 'You want to register? Enter the deets below you numpty'; - - $this->usingThemeFolder(function (string $folder) use ($loginMessage, $registerMessage) { - $viewDir = theme_path('auth/parts'); - mkdir($viewDir, 0777, true); - file_put_contents($viewDir . '/login-message.blade.php', $loginMessage); - file_put_contents($viewDir . '/register-message.blade.php', $registerMessage); - $this->setSettings(['registration-enabled' => 'true']); - - $this->get('/login')->assertSee($loginMessage); - $this->get('/register')->assertSee($registerMessage); - }); - } - - public function test_header_links_start_template_file_can_be_used() - { - $content = 'This is added text in the header bar'; - - $this->usingThemeFolder(function (string $folder) use ($content) { - $viewDir = theme_path('layouts/parts'); - mkdir($viewDir, 0777, true); - file_put_contents($viewDir . '/header-links-start.blade.php', $content); - $this->setSettings(['registration-enabled' => 'true']); - - $this->get('/login')->assertSee($content); - }); - } - - public function test_custom_settings_category_page_can_be_added_via_view_file() - { - $content = 'My SuperCustomSettings'; - - $this->usingThemeFolder(function (string $folder) use ($content) { - $viewDir = theme_path('settings/categories'); - mkdir($viewDir, 0777, true); - file_put_contents($viewDir . '/beans.blade.php', $content); - - $this->asAdmin()->get('/settings/beans')->assertSee($content); - }); - } - - public function test_public_folder_contents_accessible_via_route() - { - $this->usingThemeFolder(function (string $themeFolderName) { - $publicDir = theme_path('public'); - mkdir($publicDir, 0777, true); - - $text = 'some-text ' . md5(random_bytes(5)); - $css = "body { background-color: tomato !important; }"; - file_put_contents("{$publicDir}/file.txt", $text); - file_put_contents("{$publicDir}/file.css", $css); - copy($this->files->testFilePath('test-image.png'), "{$publicDir}/image.png"); - - $resp = $this->asAdmin()->get("/theme/{$themeFolderName}/file.txt"); - $resp->assertStreamedContent($text); - $resp->assertHeader('Content-Type', 'text/plain; charset=utf-8'); - $resp->assertHeader('Cache-Control', 'max-age=86400, private'); - - $resp = $this->asAdmin()->get("/theme/{$themeFolderName}/image.png"); - $resp->assertHeader('Content-Type', 'image/png'); - $resp->assertHeader('Cache-Control', 'max-age=86400, private'); - - $resp = $this->asAdmin()->get("/theme/{$themeFolderName}/file.css"); - $resp->assertStreamedContent($css); - $resp->assertHeader('Content-Type', 'text/css; charset=utf-8'); - $resp->assertHeader('Cache-Control', 'max-age=86400, private'); - }); - } - - public function test_theme_register_views_event_to_insert_views_before_and_after() + public function test_register_views_to_insert_views_before_and_after() { $this->usingThemeFolder(function (string $folder) { $before = 'this-is-my-before-header-string'; @@ -530,31 +318,4 @@ public function test_theme_register_views_event_to_insert_views_before_and_after $this->artisan('view:clear'); } - - protected function usingThemeFolder(callable $callback) - { - // Create a folder and configure a theme - $themeFolderName = 'testing_theme_' . str_shuffle(rtrim(base64_encode(time()), '=')); - config()->set('view.theme', $themeFolderName); - $themeFolderPath = theme_path(''); - - // Create theme folder and clean it up on application tear-down - File::makeDirectory($themeFolderPath); - $this->beforeApplicationDestroyed(fn() => File::deleteDirectory($themeFolderPath)); - - // Run provided callback with theme env option set - $this->runWithEnv(['APP_THEME' => $themeFolderName], function () use ($callback, $themeFolderName) { - call_user_func($callback, $themeFolderName); - }); - } -} - -class MyCustomCommand extends Command -{ - protected $signature = 'bookstack:test-custom-command'; - - public function handle() - { - $this->line('Command ran!'); - } } diff --git a/tests/Theme/LogicalThemeTest.php b/tests/Theme/LogicalThemeTest.php new file mode 100644 index 00000000000..feb1c7ea78a --- /dev/null +++ b/tests/Theme/LogicalThemeTest.php @@ -0,0 +1,105 @@ +usingThemeFolder(function ($themeFolder) { + $functionsFile = theme_path('functions.php'); + app()->alias('cat', 'dog'); + file_put_contents($functionsFile, "alias('cat', 'dog');});"); + $this->runWithEnv(['APP_THEME' => $themeFolder], function () { + $this->assertEquals('cat', $this->app->getAlias('dog')); + }); + }); + } + + public function test_theme_functions_loads_errors_are_caught_and_logged() + { + $this->usingThemeFolder(function ($themeFolder) { + $functionsFile = theme_path('functions.php'); + file_put_contents($functionsFile, "expectException(ThemeException::class); + $this->expectExceptionMessageMatches('/Failed loading theme functions file at ".*?" with error: Class "BookStack\\\\Biscuits" not found/'); + + $this->runWithEnv(['APP_THEME' => $themeFolder], fn() => null); + }); + } + + public function test_add_social_driver() + { + Theme::addSocialDriver('catnet', [ + 'client_id' => 'abc123', + 'client_secret' => 'def456', + ], 'SocialiteProviders\Discord\DiscordExtendSocialite@handleTesting'); + + $this->assertEquals('catnet', config('services.catnet.name')); + $this->assertEquals('abc123', config('services.catnet.client_id')); + $this->assertEquals(url('/login/service/catnet/callback'), config('services.catnet.redirect')); + + $loginResp = $this->get('/login'); + $loginResp->assertSee('login/service/catnet'); + } + + public function test_add_social_driver_uses_name_in_config_if_given() + { + Theme::addSocialDriver('catnet', [ + 'client_id' => 'abc123', + 'client_secret' => 'def456', + 'name' => 'Super Cat Name', + ], 'SocialiteProviders\Discord\DiscordExtendSocialite@handleTesting'); + + $this->assertEquals('Super Cat Name', config('services.catnet.name')); + $loginResp = $this->get('/login'); + $loginResp->assertSee('Super Cat Name'); + } + + public function test_add_social_driver_allows_a_configure_for_redirect_callback_to_be_passed() + { + Theme::addSocialDriver( + 'discord', + [ + 'client_id' => 'abc123', + 'client_secret' => 'def456', + 'name' => 'Super Cat Name', + ], + 'SocialiteProviders\Discord\DiscordExtendSocialite@handle', + function ($driver) { + $driver->with(['donkey' => 'donut']); + } + ); + + $loginResp = $this->get('/login/service/discord'); + $redirect = $loginResp->headers->get('location'); + $this->assertStringContainsString('donkey=donut', $redirect); + } + + public function test_register_command_allows_provided_command_to_be_usable_via_artisan() + { + Theme::registerCommand(new MyCustomCommand()); + + Artisan::call('bookstack:test-custom-command', []); + $output = Artisan::output(); + + $this->assertStringContainsString('Command ran!', $output); + } +} + +class MyCustomCommand extends Command +{ + protected $signature = 'bookstack:test-custom-command'; + + public function handle() + { + $this->line('Command ran!'); + } +} diff --git a/tests/Theme/VisualThemeTest.php b/tests/Theme/VisualThemeTest.php new file mode 100644 index 00000000000..c06807d7fd0 --- /dev/null +++ b/tests/Theme/VisualThemeTest.php @@ -0,0 +1,132 @@ +usingThemeFolder(function () { + $translationPath = theme_path('/lang/en'); + File::makeDirectory($translationPath, 0777, true); + + $customTranslations = ' \'Sandwiches\']; + '; + file_put_contents($translationPath . '/entities.php', $customTranslations); + + $homeRequest = $this->actingAs($this->users->viewer())->get('/'); + $this->withHtml($homeRequest)->assertElementContains('header nav', 'Sandwiches'); + }); + } + + public function test_custom_settings_category_page_can_be_added_via_view_file() + { + $content = 'My SuperCustomSettings'; + + $this->usingThemeFolder(function (string $folder) use ($content) { + $viewDir = theme_path('settings/categories'); + mkdir($viewDir, 0777, true); + file_put_contents($viewDir . '/beans.blade.php', $content); + + $this->asAdmin()->get('/settings/beans')->assertSee($content); + }); + } + + public function test_base_body_start_and_end_template_files_can_be_used() + { + $bodyStartStr = 'barry-fought-against-the-panther'; + $bodyEndStr = 'barry-lost-his-fight-with-grace'; + + $this->usingThemeFolder(function (string $folder) use ($bodyStartStr, $bodyEndStr) { + $viewDir = theme_path('layouts/parts'); + mkdir($viewDir, 0777, true); + file_put_contents($viewDir . '/base-body-start.blade.php', $bodyStartStr); + file_put_contents($viewDir . '/base-body-end.blade.php', $bodyEndStr); + + $resp = $this->asEditor()->get('/'); + $resp->assertSee($bodyStartStr); + $resp->assertSee($bodyEndStr); + }); + } + + public function test_export_body_start_and_end_template_files_can_be_used() + { + $bodyStartStr = 'garry-fought-against-the-panther'; + $bodyEndStr = 'garry-lost-his-fight-with-grace'; + $page = $this->entities->page(); + + $this->usingThemeFolder(function (string $folder) use ($bodyStartStr, $bodyEndStr, $page) { + $viewDir = theme_path('layouts/parts'); + mkdir($viewDir, 0777, true); + file_put_contents($viewDir . '/export-body-start.blade.php', $bodyStartStr); + file_put_contents($viewDir . '/export-body-end.blade.php', $bodyEndStr); + + $resp = $this->asEditor()->get($page->getUrl('/export/html')); + $resp->assertSee($bodyStartStr); + $resp->assertSee($bodyEndStr); + }); + } + + public function test_login_and_register_message_template_files_can_be_used() + { + $loginMessage = 'Welcome to this instance, login below you scallywag'; + $registerMessage = 'You want to register? Enter the deets below you numpty'; + + $this->usingThemeFolder(function (string $folder) use ($loginMessage, $registerMessage) { + $viewDir = theme_path('auth/parts'); + mkdir($viewDir, 0777, true); + file_put_contents($viewDir . '/login-message.blade.php', $loginMessage); + file_put_contents($viewDir . '/register-message.blade.php', $registerMessage); + $this->setSettings(['registration-enabled' => 'true']); + + $this->get('/login')->assertSee($loginMessage); + $this->get('/register')->assertSee($registerMessage); + }); + } + + public function test_header_links_start_template_file_can_be_used() + { + $content = 'This is added text in the header bar'; + + $this->usingThemeFolder(function (string $folder) use ($content) { + $viewDir = theme_path('layouts/parts'); + mkdir($viewDir, 0777, true); + file_put_contents($viewDir . '/header-links-start.blade.php', $content); + $this->setSettings(['registration-enabled' => 'true']); + + $this->get('/login')->assertSee($content); + }); + } + + public function test_public_folder_contents_accessible_via_route() + { + $this->usingThemeFolder(function (string $themeFolderName) { + $publicDir = theme_path('public'); + mkdir($publicDir, 0777, true); + + $text = 'some-text ' . md5(random_bytes(5)); + $css = "body { background-color: tomato !important; }"; + file_put_contents("{$publicDir}/file.txt", $text); + file_put_contents("{$publicDir}/file.css", $css); + copy($this->files->testFilePath('test-image.png'), "{$publicDir}/image.png"); + + $resp = $this->asAdmin()->get("/theme/{$themeFolderName}/file.txt"); + $resp->assertStreamedContent($text); + $resp->assertHeader('Content-Type', 'text/plain; charset=utf-8'); + $resp->assertHeader('Cache-Control', 'max-age=86400, private'); + + $resp = $this->asAdmin()->get("/theme/{$themeFolderName}/image.png"); + $resp->assertHeader('Content-Type', 'image/png'); + $resp->assertHeader('Cache-Control', 'max-age=86400, private'); + + $resp = $this->asAdmin()->get("/theme/{$themeFolderName}/file.css"); + $resp->assertStreamedContent($css); + $resp->assertHeader('Content-Type', 'text/css; charset=utf-8'); + $resp->assertHeader('Cache-Control', 'max-age=86400, private'); + }); + } +} From 120ee38383c1a07f03405e88406a565dad1acc67 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 1 Feb 2026 17:31:21 +0000 Subject: [PATCH 035/204] Theme Modules: Added testing coverage --- app/Theming/ThemeModule.php | 26 ++-- tests/Theme/ThemeModuleTests.php | 223 +++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+), 12 deletions(-) create mode 100644 tests/Theme/ThemeModuleTests.php diff --git a/app/Theming/ThemeModule.php b/app/Theming/ThemeModule.php index f873ed247bd..594aa0701c7 100644 --- a/app/Theming/ThemeModule.php +++ b/app/Theming/ThemeModule.php @@ -6,17 +6,20 @@ class ThemeModule { - protected string $name; - protected string $description; - protected string $folderName; - protected string $version; + public function __construct( + public readonly string $name, + public readonly string $description, + public readonly string $folderName, + public readonly string $version, + ) { + } /** * Create a ThemeModule instance from JSON data. * * @throws ThemeException */ - public static function fromJson(array $data, string $folderName): static + public static function fromJson(array $data, string $folderName): self { if (empty($data['name']) || !is_string($data['name'])) { throw new ThemeException("Module in folder \"{$folderName}\" is missing a valid 'name' property"); @@ -34,13 +37,12 @@ public static function fromJson(array $data, string $folderName): static throw new ThemeException("Module in folder \"{$folderName}\" has an invalid 'version' format. Expected semantic version format like '1.0.0' or 'v1.0.0'"); } - $module = new static(); - $module->name = $data['name']; - $module->description = $data['description']; - $module->folderName = $folderName; - $module->version = $data['version']; - - return $module; + return new self( + name: $data['name'], + description: $data['description'], + folderName: $folderName, + version: $data['version'], + ); } /** diff --git a/tests/Theme/ThemeModuleTests.php b/tests/Theme/ThemeModuleTests.php new file mode 100644 index 00000000000..a7d317dceaf --- /dev/null +++ b/tests/Theme/ThemeModuleTests.php @@ -0,0 +1,223 @@ +usingThemeFolder(function ($themeFolder) { + $a = theme_path('modules/a'); + $b = theme_path('modules/b'); + mkdir($a, 0777, true); + mkdir($b, 0777, true); + + file_put_contents($a . '/bookstack-module.json', json_encode([ + 'name' => 'Module A', + 'description' => 'This is module A', + 'version' => '1.0.0', + ])); + file_put_contents($b . '/bookstack-module.json', json_encode([ + 'name' => 'Module B', + 'description' => 'This is module B', + 'version' => 'v0.5.0', + ])); + + $this->refreshApplication(); + + $modules = Theme::getModules(); + $this->assertCount(2, $modules); + + $moduleA = $modules['a']; + $this->assertEquals('Module A', $moduleA->name); + $this->assertEquals('This is module A', $moduleA->description); + $this->assertEquals('1.0.0', $moduleA->version); + }); + } + + public function test_module_not_loaded_if_no_bookstack_module_json() + { + $this->usingThemeFolder(function ($themeFolder) { + $moduleDir = theme_path('/modules/a'); + mkdir($moduleDir, 0777, true); + file_put_contents($moduleDir . '/module.json', '{}'); + $this->refreshApplication(); + $modules = Theme::getModules(); + $this->assertCount(0, $modules); + }); + } + + public function test_language_text_overridable_via_module() + { + $this->usingModuleFolder(function (string $moduleFolderPath) { + $translationPath = $moduleFolderPath . '/lang/en'; + mkdir($translationPath, 0777, true); + file_put_contents($translationPath . '/entities.php', ' "SuperBeans"];'); + $this->refreshApplication(); + + $this->asAdmin()->get('/books')->assertSee('SuperBeans'); + }); + } + + public function test_language_files_merge_with_theme_files_with_theme_taking_precedence() + { + $this->usingModuleFolder(function (string $moduleFolderPath) { + $moduleTranslationPath = $moduleFolderPath . '/lang/en'; + mkdir($moduleTranslationPath, 0777, true); + file_put_contents($moduleTranslationPath . '/entities.php', ' "SuperBeans", "recently_viewed" => "ViewedBiscuits"];'); + + $themeTranslationPath = theme_path('lang/en'); + mkdir($themeTranslationPath, 0777, true); + file_put_contents($themeTranslationPath . '/entities.php', ' "WonderBeans"];'); + $this->refreshApplication(); + + $this->asAdmin()->get('/books') + ->assertSee('WonderBeans') + ->assertDontSee('SuperBeans') + ->assertSee('ViewedBiscuits'); + }); + } + + public function test_view_files_overridable_from_module() + { + $this->usingModuleFolder(function (string $moduleFolderPath) { + $viewsFolder = $moduleFolderPath . '/views/layouts/parts'; + mkdir($viewsFolder, 0777, true); + file_put_contents($viewsFolder . '/header.blade.php', 'My custom header that says badgerriffic'); + $this->refreshApplication(); + $this->asAdmin()->get('/')->assertSee('badgerriffic'); + }); + } + + public function test_theme_view_files_take_precedence_over_module_view_files() + { + $this->usingModuleFolder(function (string $moduleFolderPath) { + $viewsFolder = $moduleFolderPath . '/views/layouts/parts'; + mkdir($viewsFolder, 0777, true); + file_put_contents($viewsFolder . '/header.blade.php', 'My custom header that says badgerriffic'); + + $themeViewsFolder = theme_path('layouts/parts'); + mkdir($themeViewsFolder, 0777, true); + file_put_contents($themeViewsFolder . '/header.blade.php', 'My theme header that says awesomeferrets'); + + $this->refreshApplication(); + $this->asAdmin()->get('/') + ->assertDontSee('badgerriffic') + ->assertSee('awesomeferrets'); + }); + } + + public function test_theme_and_modules_views_can_be_used_at_the_same_time() + { + $this->usingModuleFolder(function (string $moduleFolderPath) { + $viewsFolder = $moduleFolderPath . '/views/layouts/parts'; + mkdir($viewsFolder, 0777, true); + file_put_contents($viewsFolder . '/base-body-start.blade.php', 'My custom header that says badgerriffic'); + + $themeViewsFolder = theme_path('layouts/parts'); + mkdir($themeViewsFolder, 0777, true); + file_put_contents($themeViewsFolder . '/base-body-end.blade.php', 'My theme header that says awesomeferrets'); + + $this->refreshApplication(); + $this->asAdmin()->get('/') + ->assertSee('badgerriffic') + ->assertSee('awesomeferrets'); + }); + } + + public function test_icons_can_be_overridden_from_module() + { + $this->usingModuleFolder(function (string $moduleFolderPath) { + $iconsFolder = $moduleFolderPath . '/icons'; + mkdir($iconsFolder, 0777, true); + file_put_contents($iconsFolder . '/books.svg', ''); + $this->refreshApplication(); + + $this->asAdmin()->get('/')->assertSee('supericonpath', false); + }); + } + + public function test_theme_icons_take_precedence_over_module_icons() + { + $this->usingModuleFolder(function (string $moduleFolderPath) { + $iconsFolder = $moduleFolderPath . '/icons'; + mkdir($iconsFolder, 0777, true); + file_put_contents($iconsFolder . '/books.svg', ''); + $this->refreshApplication(); + + $themeViewsFolder = theme_path('icons'); + mkdir($themeViewsFolder, 0777, true); + file_put_contents($themeViewsFolder . '/books.svg', ''); + + + $this->asAdmin()->get('/') + ->assertSee('wackyiconpath', false) + ->assertDontSee('supericonpath', false); + }); + } + + public function test_public_folder_can_be_provided_from_module() + { + $this->usingModuleFolder(function (string $moduleFolderPath) { + $publicFolder = $moduleFolderPath . '/public'; + mkdir($publicFolder, 0777, true); + $themeName = basename(dirname(dirname($moduleFolderPath))); + file_put_contents($publicFolder . '/test.txt', 'hellofrominsidethisfileimaghostwoooo!'); + $this->refreshApplication(); + + $resp = $this->asAdmin()->get("/theme/{$themeName}/test.txt")->streamedContent(); + $this->assertEquals('hellofrominsidethisfileimaghostwoooo!', $resp); + }); + } + + public function test_theme_public_files_take_precedence_over_modules() + { + $this->usingModuleFolder(function (string $moduleFolderPath) { + $publicFolder = $moduleFolderPath . '/public'; + mkdir($publicFolder, 0777, true); + $themeName = basename(theme_path()); + file_put_contents($publicFolder . '/test.txt', 'hellofrominsidethisfileimaghostwoooo!'); + + $themePublicFolder = theme_path('public'); + mkdir($themePublicFolder, 0777, true); + file_put_contents($themePublicFolder . '/test.txt', 'imadifferentghostinsidethetheme,woooooo!'); + + $this->refreshApplication(); + + $resp = $this->asAdmin()->get("/theme/{$themeName}/test.txt")->streamedContent(); + $this->assertEquals('imadifferentghostinsidethetheme,woooooo!', $resp); + }); + } + + public function test_logical_functions_file_loaded_from_module_and_it_runs_alongside_theme_functions() + { + $this->usingModuleFolder(function (string $moduleFolderPath) { + file_put_contents($moduleFolderPath . '/functions.php', "alias('cat', 'dog');});"); + + $themeFunctionsFile = theme_path('functions.php'); + file_put_contents($themeFunctionsFile, "alias('beans', 'cheese');});"); + + $this->refreshApplication(); + + $this->assertEquals('cat', $this->app->getAlias('dog')); + $this->assertEquals('beans', $this->app->getAlias('cheese')); + }); + } + + protected function usingModuleFolder(callable $callback): void + { + $this->usingThemeFolder(function (string $themeFolder) use ($callback) { + $moduleFolderPath = theme_path('modules/test-module'); + mkdir($moduleFolderPath, 0777, true); + file_put_contents($moduleFolderPath . '/bookstack-module.json', json_encode([ + 'name' => 'Test Module', + 'description' => 'This is a test module', + 'version' => 'v1.0.0', + ])); + $callback($moduleFolderPath); + }); + } +} From aa0a8dda114e0c0872ed11f8c69b6529aadab84f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 2 Feb 2026 18:29:35 +0000 Subject: [PATCH 036/204] Theme Modules: Added dev documentation --- app/Theming/ThemeModule.php | 10 +++--- dev/docs/logical-theme-system.md | 35 +++++++++++++++++++ dev/docs/theme-system-modules.md | 60 ++++++++++++++++++++++++++++++++ dev/docs/visual-theme-system.md | 5 ++- 4 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 dev/docs/theme-system-modules.md diff --git a/app/Theming/ThemeModule.php b/app/Theming/ThemeModule.php index 594aa0701c7..12b1486beda 100644 --- a/app/Theming/ThemeModule.php +++ b/app/Theming/ThemeModule.php @@ -4,13 +4,13 @@ use BookStack\Exceptions\ThemeException; -class ThemeModule +readonly class ThemeModule { public function __construct( - public readonly string $name, - public readonly string $description, - public readonly string $folderName, - public readonly string $version, + public string $name, + public string $description, + public string $folderName, + public string $version, ) { } diff --git a/dev/docs/logical-theme-system.md b/dev/docs/logical-theme-system.md index 0063c4e8bac..9457ca78b52 100644 --- a/dev/docs/logical-theme-system.md +++ b/dev/docs/logical-theme-system.md @@ -99,6 +99,41 @@ Theme::listen(ThemeEvents::APP_BOOT, function($app) { }); ``` +## Custom View Registration Example + +Using the logical theme system, you can register custom views to be rendered before/after other existing views, providing a flexible way to add content without needing to override and/or replicate existing content. This is done by listening to the `THEME_REGISTER_VIEWS`. + +**Note:** You don't need to use this to override existing views, or register whole new main views to use, since that's done automatically based on their existence. This is just for advanced capabilities like inserting before/after existing views. + +This event provides a `ThemeViews` instance which has the following methods made available: + +- `renderBefore(string $targetView, string $localView, int $priority)` +- `renderAfter(string $targetView, string $localView, int $priority)` + +The target view is the name of that which we want to insert our custom view relative to. +The local view is the name of the view we want to add and render. +The priority provides a suggestion to the ordering of view display, with lower numbers being shown first. This defaults to 50 if not provided. + +Here's an example of this in use: + +```php +renderBefore('layouts.parts.header', 'welcome-banner', 4); + $themeViews->renderAfter('layouts.parts.header', 'information-alert'); + $themeViews->renderAfter('layouts.parts.header', 'additions.password-notice', 20); +}); +``` + +In this example, we're inserting custom views before and after the main header bar. +BookStack will look for a `welcome-banner.blade.php` file within our theme folder (or a theme module view folder) to render before the header. It'll look for the `information-alert.blade.php` and `additions/password-notice.blade.php` views to render afterwards. +The password notice will be shown above the information alert view, since it has a specified priority of 20, whereas the information alert view would default to a priority of 50. + ## Custom Command Registration Example The logical theme system supports adding custom [artisan commands](https://laravel.com/docs/8.x/artisan) to BookStack. diff --git a/dev/docs/theme-system-modules.md b/dev/docs/theme-system-modules.md new file mode 100644 index 00000000000..c25a6024113 --- /dev/null +++ b/dev/docs/theme-system-modules.md @@ -0,0 +1,60 @@ +# Theme System Modules + +A theme system module is a collection of customizations using the [visual](visual-theme-system.md) and [logical](logical-theme-system.md) theme systems, provided along with some metadata, that can be installed alongside other modules within a theme. They can effectively be thought of as "plugins" or "extensions" that can be applied in addition to any customizations in the active theme. + +### Module Location + +Modules are contained within a folder themselves, which should be located inside a `modules` folder within a [BookStack theme folder](visual-theme-system.md#getting-started). +As an example, starting from the `themes/` top-level folder of a BookStack instance: + +```txt +themes +└── my-theme + └── modules + ├── module-a + │ └── bookstack-module.json + └── module-b + └── bookstack-module.json +``` + +### Module Format + +A module exists as a folder in the location [as detailed above](#module-location). +The content within the module folder should then follow this format: + +- `bookstack-module.json` - REQUIRED - A JSON file containing [the metadata](#module-json-metadata) for the module. +- `functions.php` - OPTIONAL - A PHP file containing code for the [logical theme system](logical-theme-system.md). +- `icons/` - OPTIONAL - A folder containing any icons to use as per [the visual theme system](visual-theme-system.md#customizing-icons). +- `lang/` - OPTIONAL - A folder containing any language files to use as per [the visual theme system](visual-theme-system.md#customizing-text-content). +- `public/` - OPTIONAL - A folder containing any files to expose into public web-space as per [the visual theme system](visual-theme-system.md#publicly-accessible-files). +- `views/` - OPTIONAL - A folder containing any view additions or overrides as per [the visual theme system](visual-theme-system.md#customizing-view-files). + +You can create additional directories/files for your own needs within the module, but ideally name them something unique to prevent conflicts with the above structure. + +### Module JSON Metadata + +Modules are required to have a `bookstack-module.json` file in the top level directory of the module. +This must be a JSON file with the following properties: + +- `name` - string - An (ideally unique) name for the module. +- `description` - string - A short description of the module. +- `version` - string - A string version number generally following [semantic versioning](https://semver.org/). + - Examples: `v0.4.0`, `4.3.12`, `v0.1.0-beta4`. + +### Customization Order/Precedence + +It's possible that multiple modules may override/customize the same content. +Right now, there's no assurance in regard to the order in which modules may be loaded. +Generally they will be used/searched in order of their module folder name, but this is not assured and should not be relied upon. + +It's also possible that modules customize the same content as the configured theme. +In this scenario, the theme takes precedence. Modules are designed to be more portable and instance abstract, whereas the theme folder would typically be specific to the instance. +This allows the theme to be used to customize or override module content for the BookStack instance, without altering the module code itself. + +### Module Best Practices + +Here are some general best practices when it comes to creating modules: + +- Use a unique name and clear description so the user can understand the purpose of the module. +- Increment the metadata version on change, keeping to [semver](https://semver.org/) to indicate compatibility of new versions. +- Where possible, prefer to [insert views before/after](logical-theme-system.md#custom-view-registration-example) instead of overriding existing views, to reduce likelihood of conflicts or update troubles. \ No newline at end of file diff --git a/dev/docs/visual-theme-system.md b/dev/docs/visual-theme-system.md index 8a76ddb00e0..8d5669b82bf 100644 --- a/dev/docs/visual-theme-system.md +++ b/dev/docs/visual-theme-system.md @@ -4,7 +4,7 @@ BookStack allows visual customization via the theme system which enables you to This is part of the theme system alongside the [logical theme system](./logical-theme-system.md). -**Note:** This theme system itself is maintained and supported but usages of this system, including the files you are able to override, are not considered stable and may change upon any update. You should test any customizations made after updates. +**Note:** This theme system itself is maintained and supported, but usages of this system, including the files you are able to override, are not considered stable and may change upon any update. You should test any customizations made after updates. ## Getting Started @@ -18,6 +18,9 @@ You'll need to tell BookStack to use your theme via the `APP_THEME` option in yo Content placed in your `themes//` folder will override the original view files found in the `resources/views` folder. These files are typically [Laravel Blade](https://laravel.com/docs/10.x/blade) files. As an example, I could override the `resources/views/books/parts/list-item.blade.php` file with my own template at the path `themes//books/parts/list-item.blade.php`. +In addition to overriding original views, this could be used to add new views for use via the [logical theme system](logical-theme-system.md). +By using the `THEME_REGISTER_VIEWS` logical event, you can also register your views to be rendered before/after existing views. An example of this can be found in our [logical theme guidance](logical-theme-system.md#custom-view-registration-example). + ## Customizing Icons SVG files placed in a `themes//icons` folder will override any icons of the same name within `resources/icons`. You'd typically want to follow the format convention of the existing icons, where no XML deceleration is included and no width & height attributes are set, to ensure optimal compatibility. From 45ae03ceac1110eac8072b7733941fadc63cd438 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 3 Feb 2026 20:43:01 +0000 Subject: [PATCH 037/204] Theme Modules: Added install helper command Not yet tested at all, either manually or via PHPUnit --- app/Console/Commands/InstallModuleCommand.php | 249 ++++++++++++++++++ app/Theming/ThemeModule.php | 21 +- app/Theming/ThemeModuleException.php | 7 + app/Theming/ThemeModuleManager.php | 133 ++++++++++ app/Theming/ThemeModuleZip.php | 93 +++++++ app/Theming/ThemeService.php | 31 +-- dev/docs/visual-theme-system.md | 2 +- 7 files changed, 498 insertions(+), 38 deletions(-) create mode 100644 app/Console/Commands/InstallModuleCommand.php create mode 100644 app/Theming/ThemeModuleException.php create mode 100644 app/Theming/ThemeModuleManager.php create mode 100644 app/Theming/ThemeModuleZip.php diff --git a/app/Console/Commands/InstallModuleCommand.php b/app/Console/Commands/InstallModuleCommand.php new file mode 100644 index 00000000000..c2ce1a444e2 --- /dev/null +++ b/app/Console/Commands/InstallModuleCommand.php @@ -0,0 +1,249 @@ +argument('location'); + + // Get the ZIP file containing the module files + $zipPath = $this->getPathToZip($location); + if (!$zipPath) { + $this->cleanup(); + return 1; + } + + // Validate module zip file (metadata, size, etc...) and get module instance + $zip = new ThemeModuleZip($zipPath); + $themeModule = $this->validateAndGetModuleInfoFromZip($zip); + if (!$themeModule) { + $this->cleanup(); + return 1; + } + + // Get the theme folder in use, attempting to create one if no active theme in use + $themeFolder = $this->getThemeFolder(); + if (!$themeFolder) { + $this->cleanup(); + return 1; + } + + // Get the modules folder of the theme, attempting to create it if not existing, + // and create a new module manager instance. + $moduleFolder = $this->getModuleFolder($themeFolder); + $manager = new ThemeModuleManager($moduleFolder); + + // Handle existing modules with the same name + $exitingModulesWithName = $manager->getByName($themeModule->name); + $shouldContinue = $this->handleExistingModulesWithSameName($exitingModulesWithName, $manager); + if (!$shouldContinue) { + $this->cleanup(); + return 1; + } + + // Extract module ZIP into the theme modules folder + try { + $newModule = $manager->addFromZip($themeModule->name, $zip); + } catch (ThemeModuleException $exception) { + $this->error("ERROR: Failed to install module with error: {$exception->getMessage()}"); + $this->cleanup(); + return 1; + } + + $this->info("Module {$newModule->name} ({$newModule->version}) successfully installed!"); + $this->info("It has been installed at {$moduleFolder}/{$newModule->folderName}."); + $this->cleanup(); + return 0; + } + + protected function handleExistingModulesWithSameName(array $existingModules, ThemeModuleManager $manager): bool + { + if (count($existingModules) === 0) { + return true; + } + + $this->warn("The following modules already exist with the same name:"); + foreach ($existingModules as $folder => $module) { + $this->line("{$module->name} ({$folder}:{$module->version}) - {$module->description}}"); + } + $this->line(''); + + $choices = ['Cancel Module Install', 'Add Alongside Existing']; + if (count($existingModules) === 1) { + $choices[] = 'Replace Existing Module'; + } + $choice = $this->choice("What would you like to do?", $choices, 0, null, false); + if ($choice === 'Cancel Module Install') { + return false; + } + + if ($choice === 'Replace Existing Module') { + $existingModuleFolder = array_key_first($existingModules); + $this->info("Replacing existing module in {$existingModuleFolder} folder"); + $manager->deleteModuleFolder($existingModuleFolder); + } + + return true; + } + + protected function getModuleFolder(string $themeFolder): string|null + { + $path = $themeFolder . DIRECTORY_SEPARATOR . 'modules'; + if (!file_exists($path)) { + if (!is_dir($path)) { + $this->error("ERROR: Cannot create a modules folder, file already exists at {$path}"); + } + + $created = mkdir($path, 0755, true); + if (!$created) { + $this->error("ERROR: Failed to create a modules folder at {$path}"); + } + } + + return $path; + } + + protected function getThemeFolder(): string|null + { + $path = theme_path(''); + if (!$path) { + $shouldCreate = $this->confirm('No active theme folder found, would you like to create one?'); + if (!$shouldCreate) { + return null; + } + + $folder = 'custom'; + while (file_exists(base_path("themes" . DIRECTORY_SEPARATOR . $folder))) { + $folder = 'custom-' . Str::random(4); + } + + $path = base_path("themes/{$folder}"); + $created = mkdir($path, 0755, true); + if (!$created) { + $this->error('Failed to create a theme folder to use. This may be a permissions issue. Try manually configuring an active theme.'); + return null; + } + + $this->info("Created theme folder at {$path}"); + $this->warn("You will need to set APP_THEME={$folder} in your BookStack env configuration to enable this theme!"); + } + + return $path; + } + + protected function validateAndGetModuleInfoFromZip(ThemeModuleZip $zip): ThemeModule|null + { + if (!$zip->exists()) { + $this->error("ERROR: Cannot open ZIP file at {$zip->getPath()}"); + return null; + } + + if ($zip->getContentsSize() > (50 * 1024 * 1024)) { + $this->error("ERROR: Module ZIP file is too large. Maximum size is 50MB."); + return null; + } + + try { + $themeModule = $zip->getModuleInstance(); + } catch (ThemeModuleException $exception) { + $this->error("ERROR: Failed to read module metadata with error: {$exception->getMessage()}"); + return null; + } + + return $themeModule; + } + + protected function downloadModuleFile(string $location): string + { + $httpRequests = app()->make(HttpRequestService::class); + $client = $httpRequests->buildClient(30); + + $resp = $client->get($location, ['stream' => true]); + + $tempFile = tempnam(sys_get_temp_dir(), 'bookstack_module_'); + $fileHandle = fopen($tempFile, 'w'); + + stream_copy_to_stream($resp->getBody()->detach(), $fileHandle); + + fclose($fileHandle); + + $this->cleanupActions[] = function () use ($tempFile) { + unlink($tempFile); + }; + + return $tempFile; + } + + protected function getPathToZip(string $location): string|null + { + $lowerLocation = strtolower($location); + $isRemote = str_starts_with($lowerLocation, 'http://') || str_starts_with($lowerLocation, 'https://'); + + if ($isRemote) { + // Warning about fetching from source + $host = parse_url($location, PHP_URL_HOST); + $this->warn("This will download a module from {$host}. Modules can contain code which would have the ability to do anything on the BookStack host server."); + $trustHost = $this->confirm('Are you sure you trust this source?'); + if (!$trustHost) { + return null; + } + + // Check if the connection is http. If so, warn the user. + if (str_starts_with($lowerLocation, 'http://')) { + $this->warn('You are downloading a module from an insecure HTTP source. We recommend using HTTPS sources.'); + if (!$this->confirm('Do you wish to continue?')) { + return null; + } + } + + // Download ZIP and get its location + return $this->downloadModuleFile($location); + } + + // Validate file and get full location + $zipPath = realpath($location); + if (!$zipPath || !is_file($zipPath)) { + $this->error("ERROR: Module file not found at {$location}"); + return null; + } + + return $zipPath; + } + + protected function cleanup(): void + { + foreach ($this->cleanupActions as $action) { + $action(); + } + } +} diff --git a/app/Theming/ThemeModule.php b/app/Theming/ThemeModule.php index 12b1486beda..ab5fc6145db 100644 --- a/app/Theming/ThemeModule.php +++ b/app/Theming/ThemeModule.php @@ -2,46 +2,44 @@ namespace BookStack\Theming; -use BookStack\Exceptions\ThemeException; - readonly class ThemeModule { public function __construct( public string $name, public string $description, - public string $folderName, public string $version, + public string $folderName, ) { } /** * Create a ThemeModule instance from JSON data. * - * @throws ThemeException + * @throws ThemeModuleException */ public static function fromJson(array $data, string $folderName): self { if (empty($data['name']) || !is_string($data['name'])) { - throw new ThemeException("Module in folder \"{$folderName}\" is missing a valid 'name' property"); + throw new ThemeModuleException("Module in folder \"{$folderName}\" is missing a valid 'name' property"); } if (!isset($data['description']) || !is_string($data['description'])) { - throw new ThemeException("Module in folder \"{$folderName}\" is missing a valid 'description' property"); + throw new ThemeModuleException("Module in folder \"{$folderName}\" is missing a valid 'description' property"); } if (!isset($data['version']) || !is_string($data['version'])) { - throw new ThemeException("Module in folder \"{$folderName}\" is missing a valid 'version' property"); + throw new ThemeModuleException("Module in folder \"{$folderName}\" is missing a valid 'version' property"); } if (!preg_match('/^v?\d+\.\d+\.\d+(-.*)?$/', $data['version'])) { - throw new ThemeException("Module in folder \"{$folderName}\" has an invalid 'version' format. Expected semantic version format like '1.0.0' or 'v1.0.0'"); + throw new ThemeModuleException("Module in folder \"{$folderName}\" has an invalid 'version' format. Expected semantic version format like '1.0.0' or 'v1.0.0'"); } return new self( name: $data['name'], description: $data['description'], - folderName: $folderName, version: $data['version'], + folderName: $folderName, ); } @@ -53,4 +51,9 @@ public function path($path = ''): string $component = trim($path, '/'); return theme_path("modules/{$this->folderName}/{$component}"); } + + public function getVersion(): string + { + return str_starts_with($this->version, 'v') ? $this->version : 'v' . $this->version; + } } diff --git a/app/Theming/ThemeModuleException.php b/app/Theming/ThemeModuleException.php new file mode 100644 index 00000000000..4d296a64fa1 --- /dev/null +++ b/app/Theming/ThemeModuleException.php @@ -0,0 +1,7 @@ +|null */ + protected array|null $loadedModules = null; + + public function __construct( + protected string $modulesFolderPath + ) { + } + + /** + * @return array + */ + public function getByName(string $name): array + { + return array_filter($this->load(), fn(ThemeModule $module) => $module->getName() === $name); + } + + public function deleteModuleFolder(string $moduleFolderName): void + { + $modules = $this->load(); + $module = $modules[$moduleFolderName] ?? null; + if (!$module) { + return; + } + + $moduleFolderPath = $module->path(''); + if (!file_exists($moduleFolderPath)) { + return; + } + + $this->deleteDirectoryRecursively($moduleFolderPath); + unset($this->loadedModules[$moduleFolderName]); + } + + /** + * @throws ThemeModuleException + */ + public function addFromZip(string $name, ThemeModuleZip $zip): ThemeModule + { + $baseFolderName = Str::limit(Str::slug($name), 20); + $folderName = $baseFolderName; + while (!$baseFolderName || file_exists($this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName)) { + $folderName = ($baseFolderName ?: 'mod') . '-' . Str::random(4); + } + + $folderPath = $this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName; + $zip->extractTo($folderPath); + + $module = $this->loadFromFolder($folderName); + if (!$module) { + throw new ThemeModuleException("Failed to load module from zip file after extraction."); + } + + return $module; + } + + protected function deleteDirectoryRecursively(string $path): void + { + $items = array_diff(scandir($path), ['.', '..']); + foreach ($items as $item) { + $itemPath = $path . DIRECTORY_SEPARATOR . $item; + if (is_dir($itemPath)) { + $this->deleteDirectoryRecursively($itemPath); + } else { + $deleted = unlink($itemPath); + if (!$deleted) { + throw new ThemeModuleException("Failed to delete file at \"{$itemPath}\""); + } + } + } + rmdir($path); + } + + public function load(): array + { + if ($this->loadedModules !== null) { + return $this->loadedModules; + } + + if (!is_dir($this->modulesFolderPath)) { + return []; + } + + $subFolders = array_filter(scandir($this->modulesFolderPath), function ($item) { + return $item !== '.' && $item !== '..' && is_dir($this->modulesFolderPath . DIRECTORY_SEPARATOR . $item); + }); + + $modules = []; + + foreach ($subFolders as $folderName) { + $module = $this->loadFromFolder($folderName); + if ($module) { + $modules[$folderName] = $module; + } + } + + $this->loadedModules = $modules; + + return $modules; + } + + protected function loadFromFolder(string $folderName): ThemeModule|null + { + $moduleJsonFile = $this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName . DIRECTORY_SEPARATOR . 'bookstack-module.json'; + if (!file_exists($moduleJsonFile)) { + return null; + } + + try { + $jsonContent = file_get_contents($moduleJsonFile); + $jsonData = json_decode($jsonContent, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new ThemeModuleException("Invalid JSON in module file at \"{$moduleJsonFile}\": " . json_last_error_msg()); + } + + $module = ThemeModule::fromJson($jsonData, $folderName); + } catch (ThemeModuleException $exception) { + throw $exception; + } catch (\Exception $exception) { + throw new ThemeModuleException("Failed loading module from \"{$moduleJsonFile}\" with error: {$exception->getMessage()}"); + } + + return $module; + } +} diff --git a/app/Theming/ThemeModuleZip.php b/app/Theming/ThemeModuleZip.php new file mode 100644 index 00000000000..f6d9432b069 --- /dev/null +++ b/app/Theming/ThemeModuleZip.php @@ -0,0 +1,93 @@ +open($this->path); + $zip->extractTo($destinationPath); + $zip->close(); + } + + /** + * Read the module's JSON metadata to read it into a ThemeModule instance. + * @throws ThemeModuleException + */ + public function getModuleInstance(): ThemeModule + { + $zip = new \ZipArchive(); + $open = $zip->open($this->path); + if ($open !== true) { + throw new ThemeModuleException("Unable to open zip file at {$this->path}"); + } + + $moduleJsonText = $zip->getFromName('bookstack-module.json'); + $zip->close(); + + if ($moduleJsonText === false) { + throw new ThemeModuleException("bookstack-module.json not found within module ZIP at {$this->path}"); + } + + $moduleJson = json_decode($moduleJsonText, true); + if ($moduleJson === null) { + throw new ThemeModuleException("Could not read JSON from bookstack-module.json within module ZIP at {$this->path}"); + } + + return ThemeModule::fromJson($moduleJson, '_temp'); + } + + /** + * Get the path to the zip file. + */ + public function getPath(): string + { + return $this->path; + } + + /** + * Check if the zip file exists and that it appears to be a valid zip file. + */ + public function exists(): bool + { + if (!file_exists($this->path)) { + return false; + } + + $zip = new \ZipArchive(); + $open = $zip->open($this->path); + $zip->close(); + return $open === true; + } + + /** + * Get the total size of the zip file contents when uncompressed. + */ + public function getContentsSize(): int + { + $zip = new \ZipArchive(); + + if ($zip->open($this->path) !== true) { + return 0; + } + + $totalSize = 0; + for ($i = 0; $i < $zip->numFiles; $i++) { + $stat = $zip->statIndex($i); + if ($stat !== false) { + $totalSize += $stat['size']; + } + } + + $zip->close(); + + return $totalSize; + } +} diff --git a/app/Theming/ThemeService.php b/app/Theming/ThemeService.php index 6f31129804c..6013bb5586d 100644 --- a/app/Theming/ThemeService.php +++ b/app/Theming/ThemeService.php @@ -105,41 +105,16 @@ public function readThemeActions(): void /** * Read the modules folder and load in any valid theme modules. + * @throws ThemeModuleException */ public function loadModules(): void { $modulesFolder = theme_path('modules'); - if (!$modulesFolder || !is_dir($modulesFolder)) { + if (!$modulesFolder) { return; } - $subFolders = array_filter(scandir($modulesFolder), function ($item) use ($modulesFolder) { - return $item !== '.' && $item !== '..' && is_dir($modulesFolder . DIRECTORY_SEPARATOR . $item); - }); - - foreach ($subFolders as $folderName) { - $moduleJsonFile = $modulesFolder . DIRECTORY_SEPARATOR . $folderName . DIRECTORY_SEPARATOR . 'bookstack-module.json'; - - if (!file_exists($moduleJsonFile)) { - continue; - } - - try { - $jsonContent = file_get_contents($moduleJsonFile); - $jsonData = json_decode($jsonContent, true); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new ThemeException("Invalid JSON in module file at \"{$moduleJsonFile}\": " . json_last_error_msg()); - } - - $module = ThemeModule::fromJson($jsonData, $folderName); - $this->modules[$folderName] = $module; - } catch (ThemeException $exception) { - throw $exception; - } catch (\Exception $exception) { - throw new ThemeException("Failed loading module from \"{$moduleJsonFile}\" with error: {$exception->getMessage()}"); - } - } + $this->modules = (new ThemeModuleManager($modulesFolder))->load(); } /** diff --git a/dev/docs/visual-theme-system.md b/dev/docs/visual-theme-system.md index 8d5669b82bf..327660be52e 100644 --- a/dev/docs/visual-theme-system.md +++ b/dev/docs/visual-theme-system.md @@ -53,7 +53,7 @@ configured application theme. There are some considerations to these publicly served files: -- Only a predetermined range "web safe" content-types are currently served. +- Only a predetermined range of "web safe" content-types are currently served. - This limits running into potential insecure scenarios in serving problematic file types. - A static 1-day cache time it set on files served from this folder. - You can use alternative cache-breaking techniques (change of query string) upon changes if needed. From f7890c2dd9a7315ebfe729ce698257901a13a646 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 5 Feb 2026 17:49:35 +0000 Subject: [PATCH 038/204] Theme Modules: Fixes and improvements after manual testing - Added (limited) redirect handling to module downloads. - Adjusted wording/text for consistency and clarity. - Fixed scenarios where process was not stopped on error. - Fixed module folder creation check/logic. - Added better failed request handling to module downloads. - Updated download response streaming to monitor/limit download size. --- app/Console/Commands/InstallModuleCommand.php | 91 ++++++++++++++----- app/Theming/ThemeModuleManager.php | 4 +- 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/app/Console/Commands/InstallModuleCommand.php b/app/Console/Commands/InstallModuleCommand.php index c2ce1a444e2..b43fb6b4aa4 100644 --- a/app/Console/Commands/InstallModuleCommand.php +++ b/app/Console/Commands/InstallModuleCommand.php @@ -7,6 +7,7 @@ use BookStack\Theming\ThemeModuleException; use BookStack\Theming\ThemeModuleManager; use BookStack\Theming\ThemeModuleZip; +use GuzzleHttp\Psr7\Request; use Illuminate\Console\Command; use Illuminate\Support\Str; @@ -61,6 +62,11 @@ public function handle(): int // Get the modules folder of the theme, attempting to create it if not existing, // and create a new module manager instance. $moduleFolder = $this->getModuleFolder($themeFolder); + if (!$moduleFolder) { + $this->cleanup(); + return 1; + } + $manager = new ThemeModuleManager($moduleFolder); // Handle existing modules with the same name @@ -80,8 +86,8 @@ public function handle(): int return 1; } - $this->info("Module {$newModule->name} ({$newModule->version}) successfully installed!"); - $this->info("It has been installed at {$moduleFolder}/{$newModule->folderName}."); + $this->info("Module \"{$newModule->name}\" ({$newModule->version}) successfully installed!"); + $this->info("Install location: {$moduleFolder}/{$newModule->folderName}"); $this->cleanup(); return 0; } @@ -94,20 +100,20 @@ protected function handleExistingModulesWithSameName(array $existingModules, The $this->warn("The following modules already exist with the same name:"); foreach ($existingModules as $folder => $module) { - $this->line("{$module->name} ({$folder}:{$module->version}) - {$module->description}}"); + $this->line("{$module->name} ({$folder}:{$module->version}) - {$module->description}"); } $this->line(''); - $choices = ['Cancel Module Install', 'Add Alongside Existing']; + $choices = ['Cancel module install', 'Add alongside existing module']; if (count($existingModules) === 1) { - $choices[] = 'Replace Existing Module'; + $choices[] = 'Replace existing module'; } $choice = $this->choice("What would you like to do?", $choices, 0, null, false); - if ($choice === 'Cancel Module Install') { + if ($choice === 'Cancel module install') { return false; } - if ($choice === 'Replace Existing Module') { + if ($choice === 'Replace existing module') { $existingModuleFolder = array_key_first($existingModules); $this->info("Replacing existing module in {$existingModuleFolder} folder"); $manager->deleteModuleFolder($existingModuleFolder); @@ -119,14 +125,17 @@ protected function handleExistingModulesWithSameName(array $existingModules, The protected function getModuleFolder(string $themeFolder): string|null { $path = $themeFolder . DIRECTORY_SEPARATOR . 'modules'; - if (!file_exists($path)) { - if (!is_dir($path)) { - $this->error("ERROR: Cannot create a modules folder, file already exists at {$path}"); - } + if (file_exists($path) && !is_dir($path)) { + $this->error("ERROR: Cannot create a modules folder, file already exists at {$path}"); + return null; + } + + if (!file_exists($path)) { $created = mkdir($path, 0755, true); if (!$created) { $this->error("ERROR: Failed to create a modules folder at {$path}"); + return null; } } @@ -150,7 +159,7 @@ protected function getThemeFolder(): string|null $path = base_path("themes/{$folder}"); $created = mkdir($path, 0755, true); if (!$created) { - $this->error('Failed to create a theme folder to use. This may be a permissions issue. Try manually configuring an active theme.'); + $this->error('Failed to create a theme folder to use. This may be a permissions issue. Try manually configuring an active theme'); return null; } @@ -169,7 +178,7 @@ protected function validateAndGetModuleInfoFromZip(ThemeModuleZip $zip): ThemeMo } if ($zip->getContentsSize() > (50 * 1024 * 1024)) { - $this->error("ERROR: Module ZIP file is too large. Maximum size is 50MB."); + $this->error("ERROR: Module ZIP file is too large. Maximum size is 50MB"); return null; } @@ -183,17 +192,57 @@ protected function validateAndGetModuleInfoFromZip(ThemeModuleZip $zip): ThemeMo return $themeModule; } - protected function downloadModuleFile(string $location): string + protected function downloadModuleFile(string $location): string|null { $httpRequests = app()->make(HttpRequestService::class); - $client = $httpRequests->buildClient(30); + $client = $httpRequests->buildClient(30, ['stream' => true]); + $originalHost = parse_url($location, PHP_URL_HOST); + $currentLocation = $location; + $maxRedirects = 3; + $redirectCount = 0; + + // Follow redirects up to 3 times for the same hostname + do { + $resp = $client->sendRequest(new Request('GET', $currentLocation)); + $statusCode = $resp->getStatusCode(); + + if ($statusCode >= 300 && $statusCode < 400 && $redirectCount < $maxRedirects) { + $redirectLocation = $resp->getHeaderLine('Location'); + if ($redirectLocation) { + $redirectHost = parse_url($redirectLocation, PHP_URL_HOST); + if ($redirectHost === $originalHost) { + $currentLocation = $redirectLocation; + $redirectCount++; + continue; + } + } + } - $resp = $client->get($location, ['stream' => true]); + break; + } while (true); + + if ($resp->getStatusCode() >= 300) { + $this->error("ERROR: Failed to download module from {$location}"); + $this->error("Download failed with status code {$resp->getStatusCode()}"); + return null; + } $tempFile = tempnam(sys_get_temp_dir(), 'bookstack_module_'); $fileHandle = fopen($tempFile, 'w'); - - stream_copy_to_stream($resp->getBody()->detach(), $fileHandle); + $respBody = $resp->getBody(); + $size = 0; + $maxSize = 50 * 1024 * 1024; + + while (!$respBody->eof()) { + fwrite($fileHandle, $respBody->read(1024)); + $size += 1024; + if ($size > $maxSize) { + fclose($fileHandle); + unlink($tempFile); + $this->error("ERROR: Module ZIP file is too large. Maximum size is 50MB"); + return ''; + } + } fclose($fileHandle); @@ -212,7 +261,7 @@ protected function getPathToZip(string $location): string|null if ($isRemote) { // Warning about fetching from source $host = parse_url($location, PHP_URL_HOST); - $this->warn("This will download a module from {$host}. Modules can contain code which would have the ability to do anything on the BookStack host server."); + $this->warn("This will download a module from {$host}. Modules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources."); $trustHost = $this->confirm('Are you sure you trust this source?'); if (!$trustHost) { return null; @@ -220,8 +269,8 @@ protected function getPathToZip(string $location): string|null // Check if the connection is http. If so, warn the user. if (str_starts_with($lowerLocation, 'http://')) { - $this->warn('You are downloading a module from an insecure HTTP source. We recommend using HTTPS sources.'); - if (!$this->confirm('Do you wish to continue?')) { + $this->warn("You are downloading a module from an insecure HTTP source.\nWe recommend only using HTTPS sources to avoid various security risks."); + if (!$this->confirm('Are you sure you want to continue without HTTPS?')) { return null; } } diff --git a/app/Theming/ThemeModuleManager.php b/app/Theming/ThemeModuleManager.php index a1227abf796..900063d47e1 100644 --- a/app/Theming/ThemeModuleManager.php +++ b/app/Theming/ThemeModuleManager.php @@ -19,7 +19,7 @@ public function __construct( */ public function getByName(string $name): array { - return array_filter($this->load(), fn(ThemeModule $module) => $module->getName() === $name); + return array_filter($this->load(), fn(ThemeModule $module) => $module->name === $name); } public function deleteModuleFolder(string $moduleFolderName): void @@ -55,7 +55,7 @@ public function addFromZip(string $name, ThemeModuleZip $zip): ThemeModule $module = $this->loadFromFolder($folderName); if (!$module) { - throw new ThemeModuleException("Failed to load module from zip file after extraction."); + throw new ThemeModuleException("Failed to load module from zip file after extraction"); } return $module; From 5038d124e1d3b3350c8ce11d84c113f8938dd140 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 5 Feb 2026 18:01:17 +0000 Subject: [PATCH 039/204] Theme modules: Updated docs to cover ZIP format --- dev/docs/theme-system-modules.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dev/docs/theme-system-modules.md b/dev/docs/theme-system-modules.md index c25a6024113..a52e3d25f1c 100644 --- a/dev/docs/theme-system-modules.md +++ b/dev/docs/theme-system-modules.md @@ -57,4 +57,12 @@ Here are some general best practices when it comes to creating modules: - Use a unique name and clear description so the user can understand the purpose of the module. - Increment the metadata version on change, keeping to [semver](https://semver.org/) to indicate compatibility of new versions. -- Where possible, prefer to [insert views before/after](logical-theme-system.md#custom-view-registration-example) instead of overriding existing views, to reduce likelihood of conflicts or update troubles. \ No newline at end of file +- Where possible, prefer to [insert views before/after](logical-theme-system.md#custom-view-registration-example) instead of overriding existing views, to reduce likelihood of conflicts or update troubles. + +### Distribution Format + +Modules are expected to be distributed as a compressed ZIP file, where the ZIP contents follow that of a module folder. +BookStack provides a `php artisan bookstack:install-module` command which allows modules to be installed from these ZIP files, either from a local path or from a web URL. +Currently, there's a hardcoded total filesize limit of 50MB for module contents installed via this method. + +There is not yet any direct update mechanism for modules, although this is something we may introduce in the future. \ No newline at end of file From 9d3d0a4a0755946c6dfcea822c90edf47c63697a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 5 Feb 2026 21:57:12 +0000 Subject: [PATCH 040/204] Theme Modules: Added testing coverage for install command --- app/Console/Commands/InstallModuleCommand.php | 21 +- app/Theming/ThemeModuleZip.php | 19 +- tests/Commands/InstallModuleCommandTest.php | 289 ++++++++++++++++++ 3 files changed, 315 insertions(+), 14 deletions(-) create mode 100644 tests/Commands/InstallModuleCommandTest.php diff --git a/app/Console/Commands/InstallModuleCommand.php b/app/Console/Commands/InstallModuleCommand.php index b43fb6b4aa4..dc3c9363e3d 100644 --- a/app/Console/Commands/InstallModuleCommand.php +++ b/app/Console/Commands/InstallModuleCommand.php @@ -86,12 +86,15 @@ public function handle(): int return 1; } - $this->info("Module \"{$newModule->name}\" ({$newModule->version}) successfully installed!"); + $this->info("Module \"{$newModule->name}\" ({$newModule->getVersion()}) successfully installed!"); $this->info("Install location: {$moduleFolder}/{$newModule->folderName}"); $this->cleanup(); return 0; } + /** + * @param ThemeModule[] $existingModules + */ protected function handleExistingModulesWithSameName(array $existingModules, ThemeModuleManager $manager): bool { if (count($existingModules) === 0) { @@ -100,7 +103,7 @@ protected function handleExistingModulesWithSameName(array $existingModules, The $this->warn("The following modules already exist with the same name:"); foreach ($existingModules as $folder => $module) { - $this->line("{$module->name} ({$folder}:{$module->version}) - {$module->description}"); + $this->line("{$module->name} ({$folder}:{$module->getVersion()}) - {$module->description}"); } $this->line(''); @@ -145,7 +148,7 @@ protected function getModuleFolder(string $themeFolder): string|null protected function getThemeFolder(): string|null { $path = theme_path(''); - if (!$path) { + if (!$path || !is_dir($path)) { $shouldCreate = $this->confirm('No active theme folder found, would you like to create one?'); if (!$shouldCreate) { return null; @@ -178,7 +181,7 @@ protected function validateAndGetModuleInfoFromZip(ThemeModuleZip $zip): ThemeMo } if ($zip->getContentsSize() > (50 * 1024 * 1024)) { - $this->error("ERROR: Module ZIP file is too large. Maximum size is 50MB"); + $this->error("ERROR: Module ZIP file contents are too large. Maximum size is 50MB"); return null; } @@ -196,7 +199,7 @@ protected function downloadModuleFile(string $location): string|null { $httpRequests = app()->make(HttpRequestService::class); $client = $httpRequests->buildClient(30, ['stream' => true]); - $originalHost = parse_url($location, PHP_URL_HOST); + $originalUrl = parse_url($location); $currentLocation = $location; $maxRedirects = 3; $redirectCount = 0; @@ -209,8 +212,12 @@ protected function downloadModuleFile(string $location): string|null if ($statusCode >= 300 && $statusCode < 400 && $redirectCount < $maxRedirects) { $redirectLocation = $resp->getHeaderLine('Location'); if ($redirectLocation) { - $redirectHost = parse_url($redirectLocation, PHP_URL_HOST); - if ($redirectHost === $originalHost) { + $redirectUrl = parse_url($redirectLocation); + if ( + ($originalUrl['host'] ?? '') === ($redirectUrl['host'] ?? '') + && ($originalUrl['scheme'] ?? '') === ($redirectUrl['scheme'] ?? '') + && ($originalUrl['port'] ?? '') === ($redirectUrl['port'] ?? '') + ) { $currentLocation = $redirectLocation; $redirectCount++; continue; diff --git a/app/Theming/ThemeModuleZip.php b/app/Theming/ThemeModuleZip.php index f6d9432b069..7029fa0c6a0 100644 --- a/app/Theming/ThemeModuleZip.php +++ b/app/Theming/ThemeModuleZip.php @@ -2,6 +2,8 @@ namespace BookStack\Theming; +use ZipArchive; + readonly class ThemeModuleZip { public function __construct( @@ -11,7 +13,7 @@ public function __construct( public function extractTo(string $destinationPath): void { - $zip = new \ZipArchive(); + $zip = new ZipArchive(); $zip->open($this->path); $zip->extractTo($destinationPath); $zip->close(); @@ -23,7 +25,7 @@ public function extractTo(string $destinationPath): void */ public function getModuleInstance(): ThemeModule { - $zip = new \ZipArchive(); + $zip = new ZipArchive(); $open = $zip->open($this->path); if ($open !== true) { throw new ThemeModuleException("Unable to open zip file at {$this->path}"); @@ -61,10 +63,13 @@ public function exists(): bool return false; } - $zip = new \ZipArchive(); - $open = $zip->open($this->path); - $zip->close(); - return $open === true; + $zip = new ZipArchive(); + $open = $zip->open($this->path, ZipArchive::RDONLY); + if ($open === true) { + $zip->close(); + return true; + } + return false; } /** @@ -72,7 +77,7 @@ public function exists(): bool */ public function getContentsSize(): int { - $zip = new \ZipArchive(); + $zip = new ZipArchive(); if ($zip->open($this->path) !== true) { return 0; diff --git a/tests/Commands/InstallModuleCommandTest.php b/tests/Commands/InstallModuleCommandTest.php new file mode 100644 index 00000000000..0872efc3f26 --- /dev/null +++ b/tests/Commands/InstallModuleCommandTest.php @@ -0,0 +1,289 @@ +usingThemeFolder(function () { + $zip = $this->getModuleZipPath(); + $expectedInstallPath = theme_path('modules/test-module'); + $this->artisan('bookstack:install-module', ['location' => $zip]) + ->expectsOutput('Module "Test Module" (v1.0.0) successfully installed!') + ->expectsOutput("Install location: {$expectedInstallPath}") + ->assertExitCode(0); + + $this->assertDirectoryExists($expectedInstallPath); + $this->assertFileExists($expectedInstallPath . '/bookstack-module.json'); + }); + } + + public function test_remote_module_install_with_active_theme() + { + $this->usingThemeFolder(function () { + $zip = $this->getModuleZipPath(); + + $http = $this->mockHttpClient([ + new Response(200, ['Content-Length' => filesize($zip)], file_get_contents($zip)) + ]); + $expectedInstallPath = theme_path('modules/test-module'); + + $this->artisan('bookstack:install-module', ['location' => 'https://example.com/test-module.zip']) + ->expectsOutput("This will download a module from example.com. Modules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.") + ->expectsConfirmation('Are you sure you trust this source?', 'yes') + ->expectsOutput('Module "Test Module" (v1.0.0) successfully installed!') + ->expectsOutput("Install location: {$expectedInstallPath}") + ->assertExitCode(0); + + $this->assertEquals(1, $http->requestCount()); + $request = $http->requestAt(0); + $this->assertEquals('/test-module.zip', $request->getUri()->getPath()); + + $this->assertDirectoryExists($expectedInstallPath); + $this->assertFileExists($expectedInstallPath . '/bookstack-module.json'); + }); + } + + public function test_remote_http_module_warns_and_prompts_users() + { + $this->usingThemeFolder(function () { + $zip = $this->getModuleZipPath(); + + $http = $this->mockHttpClient([ + new Response(200, ['Content-Length' => filesize($zip)], file_get_contents($zip)) + ]); + $expectedInstallPath = theme_path('modules/test-module'); + + $this->artisan('bookstack:install-module', ['location' => 'http://example.com/test-module.zip']) + ->expectsOutput("This will download a module from example.com. Modules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.") + ->expectsConfirmation('Are you sure you trust this source?', 'yes') + ->expectsOutput("You are downloading a module from an insecure HTTP source.\nWe recommend only using HTTPS sources to avoid various security risks.") + ->expectsConfirmation('Are you sure you want to continue without HTTPS?', 'yes') + ->expectsOutput('Module "Test Module" (v1.0.0) successfully installed!') + ->expectsOutput("Install location: {$expectedInstallPath}") + ->assertExitCode(0); + + $request = $http->requestAt(0); + $this->assertEquals('/test-module.zip', $request->getUri()->getPath()); + }); + } + + public function test_remote_module_install_follows_redirects() + { + $this->usingThemeFolder(function () { + $zip = $this->getModuleZipPath(); + + $http = $this->mockHttpClient([ + new Response(302, ['Location' => 'https://example.com/a-test-module.zip']), + new Response(200, ['Content-Length' => filesize($zip)], file_get_contents($zip)) + ]); + + $this->artisan('bookstack:install-module', ['location' => 'https://example.com/test-module.zip']) + ->expectsConfirmation('Are you sure you trust this source?', 'yes') + ->assertExitCode(0); + + $this->assertEquals(2, $http->requestCount()); + $this->assertEquals('/test-module.zip', $http->requestAt(0)->getUri()->getPath()); + $this->assertEquals('/a-test-module.zip', $http->requestAt(1)->getUri()->getPath()); + }); + } + + public function test_remote_module_install_does_not_follow_redirects_to_different_origin() + { + $this->usingThemeFolder(function () { + $zip = $this->getModuleZipPath(); + + $http = $this->mockHttpClient([ + new Response(302, ['Location' => 'http://example.com/a-test-module.zip']), + new Response(200, ['Content-Length' => filesize($zip)], file_get_contents($zip)) + ]); + + $this->artisan('bookstack:install-module', ['location' => 'https://example.com/test-module.zip']) + ->expectsConfirmation('Are you sure you trust this source?', 'yes') + ->assertExitCode(1); + + $this->assertEquals(1, $http->requestCount()); + $this->assertEquals('https', $http->requestAt(0)->getUri()->getScheme()); + }); + } + + public function test_remote_module_install_download_failures_are_announced_to_user() + { + $this->usingThemeFolder(function () { + $http = $this->mockHttpClient([ + new Response(404), + ]); + + $this->artisan('bookstack:install-module', ['location' => 'https://example.com/test-module.zip']) + ->expectsConfirmation('Are you sure you trust this source?', 'yes') + ->expectsOutput('ERROR: Failed to download module from https://example.com/test-module.zip') + ->expectsOutput('Download failed with status code 404') + ->assertExitCode(1); + $this->assertEquals(1, $http->requestCount()); + }); + } + + public function test_run_with_invalid_path_exits_early() + { + $this->artisan('bookstack:install-module', ['location' => '/not-found.zip']) + ->expectsOutput('ERROR: Module file not found at /not-found.zip') + ->assertExitCode(1); + } + + public function test_run_with_invalid_zip_has_early_exit() + { + $zip = $this->getModuleZipPath(); + file_put_contents($zip, 'invalid zip'); + + $this->artisan('bookstack:install-module', ['location' => $zip]) + ->expectsOutput("ERROR: Cannot open ZIP file at {$zip}") + ->assertExitCode(1); + } + + public function test_run_with_large_zip_has_early_exit() + { + $zip = $this->getModuleZipPath(null, [ + 'large-file.txt' => str_repeat('a', 1024 * 1024 * 51) + ]); + + $this->artisan('bookstack:install-module', ['location' => $zip]) + ->expectsOutput("ERROR: Module ZIP file contents are too large. Maximum size is 50MB") + ->assertExitCode(1); + } + + public function test_run_with_invalid_module_data_has_early_exit() + { + $zip = $this->getModuleZipPath([ + 'name' => 'Invalid Module', + 'description' => 'A module with invalid data', + 'version' => 'dog', + ]); + + $this->artisan('bookstack:install-module', ['location' => $zip]) + ->expectsOutput("ERROR: Failed to read module metadata with error: Module in folder \"_temp\" has an invalid 'version' format. Expected semantic version format like '1.0.0' or 'v1.0.0'") + ->assertExitCode(1); + } + + public function test_local_module_install_without_active_theme_can_setup_theme_folder() + { + $zip = $this->getModuleZipPath(); + $expectedThemePath = base_path('themes/custom'); + File::deleteDirectory($expectedThemePath); + + $this->artisan('bookstack:install-module', ['location' => $zip]) + ->expectsConfirmation('No active theme folder found, would you like to create one?', 'yes') + ->expectsOutput("Created theme folder at {$expectedThemePath}") + ->expectsOutput("You will need to set APP_THEME=custom in your BookStack env configuration to enable this theme!") + ->expectsOutput('Module "Test Module" (v1.0.0) successfully installed!') + ->assertExitCode(0); + + $this->assertDirectoryExists($expectedThemePath . '/modules/test-module'); + + File::deleteDirectory($expectedThemePath); + } + + public function test_local_module_install_with_active_theme_and_conflicting_modules_file_causes_early_exit() + { + $this->usingThemeFolder(function () { + $zip = $this->getModuleZipPath(); + File::put(theme_path('modules'), '{}'); + + $this->artisan('bookstack:install-module', ['location' => $zip]) + ->expectsOutput("ERROR: Cannot create a modules folder, file already exists at " . theme_path('modules')) + ->assertExitCode(1); + }); + } + + public function test_single_existing_module_with_same_name_replace() + { + $this->usingThemeFolder(function () { + $original = $this->createModuleFolderInCurrentTheme(['name' => 'Test Module', 'description' => 'cat', 'version' => '1.0.0']); + $new = $this->getModuleZipPath(['name' => 'Test Module', 'description' => '', 'version' => '2.0.0']); + + $this->artisan('bookstack:install-module', ['location' => $new]) + ->expectsOutput('The following modules already exist with the same name:') + ->expectsOutput('Test Module (test-module:v1.0.0) - cat') + ->expectsChoice('What would you like to do?', 'Replace existing module', ['Cancel module install', 'Add alongside existing module', 'Replace existing module']) + ->expectsOutput("Replacing existing module in test-module folder") + ->assertExitCode(0); + + $this->assertFileExists($original . '/bookstack-module.json'); + $metadata = json_decode(file_get_contents($original . '/bookstack-module.json'), true); + $this->assertEquals('2.0.0', $metadata['version']); + }); + } + + public function test_single_existing_module_with_same_name_cancel() + { + $this->usingThemeFolder(function () { + $original = $this->createModuleFolderInCurrentTheme(['name' => 'Test Module', 'description' => 'cat', 'version' => '1.0.0']); + $new = $this->getModuleZipPath(['name' => 'Test Module', 'description' => '', 'version' => '2.0.0']); + + $this->artisan('bookstack:install-module', ['location' => $new]) + ->expectsOutput('The following modules already exist with the same name:') + ->expectsOutput('Test Module (test-module:v1.0.0) - cat') + ->expectsChoice('What would you like to do?', 'Cancel module install', ['Cancel module install', 'Add alongside existing module', 'Replace existing module']) + ->assertExitCode(1); + + $this->assertFileExists($original . '/bookstack-module.json'); + $metadata = json_decode(file_get_contents($original . '/bookstack-module.json'), true); + $this->assertEquals('1.0.0', $metadata['version']); + }); + } + + public function test_single_existing_module_with_same_name_add() + { + $this->usingThemeFolder(function () { + $original = $this->createModuleFolderInCurrentTheme(['name' => 'Test Module', 'description' => 'cat', 'version' => '1.0.0']); + $new = $this->getModuleZipPath(['name' => 'Test Module', 'description' => '', 'version' => '2.0.0']); + + $this->artisan('bookstack:install-module', ['location' => $new]) + ->expectsOutput('The following modules already exist with the same name:') + ->expectsOutput('Test Module (test-module:v1.0.0) - cat') + ->expectsChoice('What would you like to do?', 'Add alongside existing module', ['Cancel module install', 'Add alongside existing module', 'Replace existing module']) + ->assertExitCode(0); + + $dirs = File::directories(theme_path('modules/')); + $this->assertCount(2, $dirs); + }); + } + + protected function createModuleFolderInCurrentTheme(array|null $metadata = null, array $extraFiles = []): string + { + $original = $this->getModuleZipPath($metadata, $extraFiles); + $targetPath = theme_path('modules/test-module'); + mkdir($targetPath, 0777, true); + $originalZip = new ZipArchive(); + $originalZip->open($original); + $originalZip->extractTo($targetPath); + $originalZip->close(); + + return $targetPath; + } + + protected function getModuleZipPath(array|null $metadata = null, array $extraFiles = []): string + { + $zip = new ZipArchive(); + $tmpFile = tempnam(sys_get_temp_dir(), 'bs-test-module'); + $zip->open($tmpFile, ZipArchive::CREATE); + + $zip->addFromString('bookstack-module.json', json_encode($metadata ?? [ + 'name' => 'Test Module', + 'description' => 'A test module for BookStack', + 'version' => '1.0.0', + ])); + + foreach ($extraFiles as $path => $contents) { + $zip->addFromString($path, $contents); + } + + $zip->close(); + return $tmpFile; + } +} From a20438b901b227fd597fec66ffa2b7b8abc30cfb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 7 Feb 2026 23:01:13 +0000 Subject: [PATCH 041/204] Theme System: Fixed theme view before/after issues - Updated the system to work with modules. - Updated module docs to consider namespacing. - Fixed view loading and registration event ordering. - Fixed checking if views are registered. --- app/App/Providers/ThemeServiceProvider.php | 4 ++-- app/Theming/ThemeViews.php | 23 ++++++++++++------- dev/docs/theme-system-modules.md | 3 +++ ...emeModuleTests.php => ThemeModuleTest.php} | 15 +++++++++++- 4 files changed, 34 insertions(+), 11 deletions(-) rename tests/Theme/{ThemeModuleTests.php => ThemeModuleTest.php} (92%) diff --git a/app/App/Providers/ThemeServiceProvider.php b/app/App/Providers/ThemeServiceProvider.php index 50c76bbf846..cca1ca236e5 100644 --- a/app/App/Providers/ThemeServiceProvider.php +++ b/app/App/Providers/ThemeServiceProvider.php @@ -35,9 +35,9 @@ public function boot(): void $themeService->readThemeActions(); $themeService->dispatch(ThemeEvents::APP_BOOT, $this->app); - $themeViews = new ThemeViews(); + $themeViews = new ThemeViews($viewFactory->getFinder()); + $themeViews->registerViewPathsForTheme($themeService->getModules()); $themeService->dispatch(ThemeEvents::THEME_REGISTER_VIEWS, $themeViews); - $themeViews->registerViewPathsForTheme($viewFactory->getFinder(), $themeService->getModules()); if ($themeViews->hasRegisteredViews()) { $viewFactory->share('__themeViews', $themeViews); Blade::directive('include', function ($expression) { diff --git a/app/Theming/ThemeViews.php b/app/Theming/ThemeViews.php index b2d0adc02f6..88769bae113 100644 --- a/app/Theming/ThemeViews.php +++ b/app/Theming/ThemeViews.php @@ -17,21 +17,26 @@ class ThemeViews */ protected array $afterViews = []; + public function __construct( + protected FileViewFinder $finder + ) { + } + /** * Register any extra paths for where we may expect views to be located - * with the provided FileViewFinder, to make custom views available for use. + * with the FileViewFinder, to make custom views available for use. * @param ThemeModule[] $modules */ - public function registerViewPathsForTheme(FileViewFinder $finder, array $modules): void + public function registerViewPathsForTheme(array $modules): void { foreach ($modules as $module) { $moduleViewsPath = $module->path('views'); if (file_exists($moduleViewsPath) && is_dir($moduleViewsPath)) { - $finder->prependLocation($moduleViewsPath); + $this->finder->prependLocation($moduleViewsPath); } } - $finder->prependLocation(theme_path()); + $this->finder->prependLocation(theme_path()); } /** @@ -70,19 +75,21 @@ public function renderAfter(string $targetView, string $localView, int $priority public function hasRegisteredViews(): bool { - return !empty($this->beforeViews) && !empty($this->afterViews); + return !empty($this->beforeViews) || !empty($this->afterViews); } protected function registerAdjacentView(array &$location, string $targetView, string $localView, int $priority = 50): void { - $viewPath = theme_path($localView . '.blade.php'); - if (!file_exists($viewPath)) { - throw new ThemeException("Expected registered view file at \"{$viewPath}\" does not exist"); + try { + $viewPath = $this->finder->find($localView); + } catch (\InvalidArgumentException $exception) { + throw new ThemeException("Expected registered view file with name \"{$localView}\" could not be found."); } if (!isset($location[$targetView])) { $location[$targetView] = []; } + $location[$targetView][$viewPath] = $priority; } diff --git a/dev/docs/theme-system-modules.md b/dev/docs/theme-system-modules.md index a52e3d25f1c..10eec2275d0 100644 --- a/dev/docs/theme-system-modules.md +++ b/dev/docs/theme-system-modules.md @@ -58,6 +58,9 @@ Here are some general best practices when it comes to creating modules: - Use a unique name and clear description so the user can understand the purpose of the module. - Increment the metadata version on change, keeping to [semver](https://semver.org/) to indicate compatibility of new versions. - Where possible, prefer to [insert views before/after](logical-theme-system.md#custom-view-registration-example) instead of overriding existing views, to reduce likelihood of conflicts or update troubles. +- When using/registering custom views, use some level of unique namespacing within the view path to prevent potential conflicts with other customizations. + - For example, I may store a view within my module as `views/my-module-name-welcome.blade.php`, to be registered as 'my-module-name-welcome'. + - This is important since views may be resolved from other modules or the active theme, which may/will override your module level view. ### Distribution Format diff --git a/tests/Theme/ThemeModuleTests.php b/tests/Theme/ThemeModuleTest.php similarity index 92% rename from tests/Theme/ThemeModuleTests.php rename to tests/Theme/ThemeModuleTest.php index a7d317dceaf..b2f912dd737 100644 --- a/tests/Theme/ThemeModuleTests.php +++ b/tests/Theme/ThemeModuleTest.php @@ -5,7 +5,7 @@ use BookStack\Facades\Theme; use Tests\TestCase; -class ThemeModuleTests extends TestCase +class ThemeModuleTest extends TestCase { public function test_modules_loaded_on_theme_load() { @@ -207,6 +207,19 @@ public function test_logical_functions_file_loaded_from_module_and_it_runs_along }); } + public function test_module_can_use_theme_view_render_functions() + { + $this->usingModuleFolder(function (string $moduleFolderPath) { + file_put_contents($moduleFolderPath . '/functions.php', " \$views->renderBefore('layouts.parts.header', 'cat', 100));"); + mkdir($moduleFolderPath . '/views', 0777, true); + file_put_contents($moduleFolderPath . '/views/cat.blade.php', 'mysupercatispouncy'); + + $this->refreshApplication(); + + $this->asAdmin()->get('/')->assertSee('mysupercatispouncy'); + }); + } + protected function usingModuleFolder(callable $callback): void { $this->usingThemeFolder(function (string $themeFolder) use ($callback) { From 984a73159fc5990e91eadd478d9a4c2b8cfa5bd9 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 8 Feb 2026 13:39:34 +0000 Subject: [PATCH 042/204] Theme modules: Updated view includes to prevent caching conflicts --- app/App/Providers/ThemeServiceProvider.php | 17 ++++++++++------- app/Theming/ThemeViews.php | 20 ++++++++++++-------- resources/views/pages/show.blade.php | 2 +- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/app/App/Providers/ThemeServiceProvider.php b/app/App/Providers/ThemeServiceProvider.php index cca1ca236e5..671e5e1df74 100644 --- a/app/App/Providers/ThemeServiceProvider.php +++ b/app/App/Providers/ThemeServiceProvider.php @@ -27,6 +27,16 @@ public function boot(): void // Boot up the theme system $themeService = $this->app->make(ThemeService::class); $viewFactory = $this->app->make('view'); + $themeViews = new ThemeViews($viewFactory->getFinder()); + + // Use a custom include so that we can insert theme views before/after includes. + // This is done, even if no theme is active, so that view caching does not create problems + // when switching between themes or when switching a theme on/off. + $viewFactory->share('__themeViews', $themeViews); + Blade::directive('include', function ($expression) { + return "handleViewInclude({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>"; + }); + if (!$themeService->getTheme()) { return; } @@ -35,14 +45,7 @@ public function boot(): void $themeService->readThemeActions(); $themeService->dispatch(ThemeEvents::APP_BOOT, $this->app); - $themeViews = new ThemeViews($viewFactory->getFinder()); $themeViews->registerViewPathsForTheme($themeService->getModules()); $themeService->dispatch(ThemeEvents::THEME_REGISTER_VIEWS, $themeViews); - if ($themeViews->hasRegisteredViews()) { - $viewFactory->share('__themeViews', $themeViews); - Blade::directive('include', function ($expression) { - return "handleViewInclude({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>"; - }); - } } } diff --git a/app/Theming/ThemeViews.php b/app/Theming/ThemeViews.php index 88769bae113..630ff9d8dad 100644 --- a/app/Theming/ThemeViews.php +++ b/app/Theming/ThemeViews.php @@ -42,16 +42,20 @@ public function registerViewPathsForTheme(array $modules): void /** * Provide the response for a blade template view include. */ - public function handleViewInclude(string $viewPath, array $data = []): string + public function handleViewInclude(string $viewPath, array $data = [], array $mergeData = []): string { if (!$this->hasRegisteredViews()) { - return view()->make($viewPath, $data)->render(); + return view()->make($viewPath, $data, $mergeData)->render(); + } + + if (str_contains('book-tree', $viewPath)) { + dd($viewPath, $data); } $viewsContent = [ - ...$this->renderViewSets($this->beforeViews[$viewPath] ?? [], $data), - view()->make($viewPath, $data)->render(), - ...$this->renderViewSets($this->afterViews[$viewPath] ?? [], $data), + ...$this->renderViewSets($this->beforeViews[$viewPath] ?? [], $data, $mergeData), + view()->make($viewPath, $data, $mergeData)->render(), + ...$this->renderViewSets($this->afterViews[$viewPath] ?? [], $data, $mergeData), ]; return implode("\n", $viewsContent); @@ -97,15 +101,15 @@ protected function registerAdjacentView(array &$location, string $targetView, st * @param array $viewSet * @return string[] */ - protected function renderViewSets(array $viewSet, array $data): array + protected function renderViewSets(array $viewSet, array $data, array $mergeData): array { $paths = array_keys($viewSet); usort($paths, function (string $a, string $b) use ($viewSet) { return $viewSet[$a] <=> $viewSet[$b]; }); - return array_map(function (string $viewPath) use ($data) { - return view()->file($viewPath, $data)->render(); + return array_map(function (string $viewPath) use ($data, $mergeData) { + return view()->file($viewPath, $data, $mergeData)->render(); }, $paths); } } diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php index fcec901571b..3338646a509 100644 --- a/resources/views/pages/show.blade.php +++ b/resources/views/pages/show.blade.php @@ -22,7 +22,7 @@ class="page-content clearfix"> @include('pages.parts.page-display') - @include('pages.parts.pointer', ['page' => $page]) + @include('pages.parts.pointer', ['page' => $page, 'commentTree' => $commentTree]) @include('entities.sibling-navigation', ['next' => $next, 'previous' => $previous]) From 057d7be0bc7eb7bc6b62136da6e98c199f82f12f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 8 Feb 2026 17:03:48 +0000 Subject: [PATCH 043/204] Views: Made index/show sidebars a lot more modular Split out each sidebar block into their own template for easier customization of those elements, and less code to manage when overriding the parent show/index views. --- resources/views/books/index.blade.php | 55 +----- .../index-sidebar-section-actions.blade.php | 25 +++ .../parts/index-sidebar-section-new.blade.php | 8 + .../index-sidebar-section-popular.blade.php | 8 + .../index-sidebar-section-recents.blade.php | 6 + .../show-sidebar-section-actions.blade.php | 61 +++++++ .../show-sidebar-section-activity.blade.php | 6 + .../show-sidebar-section-details.blade.php | 21 +++ .../show-sidebar-section-shelves.blade.php | 6 + .../parts/show-sidebar-section-tags.blade.php | 5 + resources/views/books/show.blade.php | 110 +----------- .../show-sidebar-section-actions.blade.php | 65 ++++++++ .../show-sidebar-section-details.blade.php | 38 +++++ .../parts/show-sidebar-section-tags.blade.php | 5 + resources/views/chapters/show.blade.php | 116 +------------ .../show-sidebar-section-actions.blade.php | 57 +++++++ ...show-sidebar-section-attachments.blade.php | 8 + .../show-sidebar-section-details.blade.php | 61 +++++++ .../show-sidebar-section-page-nav.blade.php | 15 ++ .../parts/show-sidebar-section-tags.blade.php | 5 + resources/views/pages/show.blade.php | 156 +----------------- resources/views/shelves/index.blade.php | 48 +----- .../index-sidebar-section-actions.blade.php | 18 ++ .../parts/index-sidebar-section-new.blade.php | 8 + .../index-sidebar-section-popular.blade.php | 8 + .../index-sidebar-section-recents.blade.php | 6 + .../show-sidebar-section-actions.blade.php | 43 +++++ .../show-sidebar-section-activity.blade.php | 6 + .../show-sidebar-section-details.blade.php | 21 +++ .../parts/show-sidebar-section-tags.blade.php | 5 + resources/views/shelves/show.blade.php | 82 +-------- 31 files changed, 540 insertions(+), 542 deletions(-) create mode 100644 resources/views/books/parts/index-sidebar-section-actions.blade.php create mode 100644 resources/views/books/parts/index-sidebar-section-new.blade.php create mode 100644 resources/views/books/parts/index-sidebar-section-popular.blade.php create mode 100644 resources/views/books/parts/index-sidebar-section-recents.blade.php create mode 100644 resources/views/books/parts/show-sidebar-section-actions.blade.php create mode 100644 resources/views/books/parts/show-sidebar-section-activity.blade.php create mode 100644 resources/views/books/parts/show-sidebar-section-details.blade.php create mode 100644 resources/views/books/parts/show-sidebar-section-shelves.blade.php create mode 100644 resources/views/books/parts/show-sidebar-section-tags.blade.php create mode 100644 resources/views/chapters/parts/show-sidebar-section-actions.blade.php create mode 100644 resources/views/chapters/parts/show-sidebar-section-details.blade.php create mode 100644 resources/views/chapters/parts/show-sidebar-section-tags.blade.php create mode 100644 resources/views/pages/parts/show-sidebar-section-actions.blade.php create mode 100644 resources/views/pages/parts/show-sidebar-section-attachments.blade.php create mode 100644 resources/views/pages/parts/show-sidebar-section-details.blade.php create mode 100644 resources/views/pages/parts/show-sidebar-section-page-nav.blade.php create mode 100644 resources/views/pages/parts/show-sidebar-section-tags.blade.php create mode 100644 resources/views/shelves/parts/index-sidebar-section-actions.blade.php create mode 100644 resources/views/shelves/parts/index-sidebar-section-new.blade.php create mode 100644 resources/views/shelves/parts/index-sidebar-section-popular.blade.php create mode 100644 resources/views/shelves/parts/index-sidebar-section-recents.blade.php create mode 100644 resources/views/shelves/parts/show-sidebar-section-actions.blade.php create mode 100644 resources/views/shelves/parts/show-sidebar-section-activity.blade.php create mode 100644 resources/views/shelves/parts/show-sidebar-section-details.blade.php create mode 100644 resources/views/shelves/parts/show-sidebar-section-tags.blade.php diff --git a/resources/views/books/index.blade.php b/resources/views/books/index.blade.php index 52d23241a6f..660c008dfb1 100644 --- a/resources/views/books/index.blade.php +++ b/resources/views/books/index.blade.php @@ -5,58 +5,11 @@ @stop @section('left') - @if($recents) -
    -
    {{ trans('entities.recently_viewed') }}
    - @include('entities.list', ['entities' => $recents, 'style' => 'compact']) -
    - @endif - - - -
    -
    {{ trans('entities.books_new') }}
    - @if(count($new) > 0) - @include('entities.list', ['entities' => $new, 'style' => 'compact']) - @else -

    {{ trans('entities.books_new_empty') }}

    - @endif -
    + @include('books.parts.index-sidebar-section-recents', ['recents' => $recents]) + @include('books.parts.index-sidebar-section-popular', ['popular' => $popular]) + @include('books.parts.index-sidebar-section-new', ['new' => $new]) @stop @section('right') - -
    -
    {{ trans('common.actions') }}
    - -
    - + @include('books.parts.index-sidebar-section-actions', ['view' => $view]) @stop diff --git a/resources/views/books/parts/index-sidebar-section-actions.blade.php b/resources/views/books/parts/index-sidebar-section-actions.blade.php new file mode 100644 index 00000000000..8f8b254c819 --- /dev/null +++ b/resources/views/books/parts/index-sidebar-section-actions.blade.php @@ -0,0 +1,25 @@ +
    +
    {{ trans('common.actions') }}
    + +
    \ No newline at end of file diff --git a/resources/views/books/parts/index-sidebar-section-new.blade.php b/resources/views/books/parts/index-sidebar-section-new.blade.php new file mode 100644 index 00000000000..a9aa52c5910 --- /dev/null +++ b/resources/views/books/parts/index-sidebar-section-new.blade.php @@ -0,0 +1,8 @@ +
    +
    {{ trans('entities.books_new') }}
    + @if(count($new) > 0) + @include('entities.list', ['entities' => $new, 'style' => 'compact']) + @else +

    {{ trans('entities.books_new_empty') }}

    + @endif +
    \ No newline at end of file diff --git a/resources/views/books/parts/index-sidebar-section-popular.blade.php b/resources/views/books/parts/index-sidebar-section-popular.blade.php new file mode 100644 index 00000000000..030c75eb9a7 --- /dev/null +++ b/resources/views/books/parts/index-sidebar-section-popular.blade.php @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/resources/views/books/parts/index-sidebar-section-recents.blade.php b/resources/views/books/parts/index-sidebar-section-recents.blade.php new file mode 100644 index 00000000000..f1a68ba4f99 --- /dev/null +++ b/resources/views/books/parts/index-sidebar-section-recents.blade.php @@ -0,0 +1,6 @@ +@if($recents) +
    +
    {{ trans('entities.recently_viewed') }}
    + @include('entities.list', ['entities' => $recents, 'style' => 'compact']) +
    +@endif \ No newline at end of file diff --git a/resources/views/books/parts/show-sidebar-section-actions.blade.php b/resources/views/books/parts/show-sidebar-section-actions.blade.php new file mode 100644 index 00000000000..8e5b5c4d74a --- /dev/null +++ b/resources/views/books/parts/show-sidebar-section-actions.blade.php @@ -0,0 +1,61 @@ +
    +
    {{ trans('common.actions') }}
    + +
    \ No newline at end of file diff --git a/resources/views/books/parts/show-sidebar-section-activity.blade.php b/resources/views/books/parts/show-sidebar-section-activity.blade.php new file mode 100644 index 00000000000..c1c5c1d3ecf --- /dev/null +++ b/resources/views/books/parts/show-sidebar-section-activity.blade.php @@ -0,0 +1,6 @@ +@if(count($activity) > 0) +
    +
    {{ trans('entities.recent_activity') }}
    + @include('common.activity-list', ['activity' => $activity]) +
    +@endif \ No newline at end of file diff --git a/resources/views/books/parts/show-sidebar-section-details.blade.php b/resources/views/books/parts/show-sidebar-section-details.blade.php new file mode 100644 index 00000000000..709d0ffd9a1 --- /dev/null +++ b/resources/views/books/parts/show-sidebar-section-details.blade.php @@ -0,0 +1,21 @@ +
    +
    {{ trans('common.details') }}
    + +
    \ No newline at end of file diff --git a/resources/views/books/parts/show-sidebar-section-shelves.blade.php b/resources/views/books/parts/show-sidebar-section-shelves.blade.php new file mode 100644 index 00000000000..9de9b95c62e --- /dev/null +++ b/resources/views/books/parts/show-sidebar-section-shelves.blade.php @@ -0,0 +1,6 @@ +@if(count($bookParentShelves) > 0) +
    +
    {{ trans('entities.shelves') }}
    + @include('entities.list', ['entities' => $bookParentShelves, 'style' => 'compact']) +
    +@endif \ No newline at end of file diff --git a/resources/views/books/parts/show-sidebar-section-tags.blade.php b/resources/views/books/parts/show-sidebar-section-tags.blade.php new file mode 100644 index 00000000000..440a780c814 --- /dev/null +++ b/resources/views/books/parts/show-sidebar-section-tags.blade.php @@ -0,0 +1,5 @@ +@if($book->tags->count() > 0) +
    + @include('entities.tag-list', ['entity' => $book]) +
    +@endif \ No newline at end of file diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index d510c8fd560..4eb83164f4d 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -67,114 +67,14 @@ @stop @section('right') -
    -
    {{ trans('common.details') }}
    - -
    - -
    -
    {{ trans('common.actions') }}
    - -
    - + @include('books.parts.show-sidebar-section-details', ['book' => $book, 'watchOptions' => $watchOptions]) + @include('books.parts.show-sidebar-section-actions', ['book' => $book, 'watchOptions' => $watchOptions]) @stop @section('left') - @include('entities.search-form', ['label' => trans('entities.books_search_this')]) - - @if($book->tags->count() > 0) -
    - @include('entities.tag-list', ['entity' => $book]) -
    - @endif - - @if(count($bookParentShelves) > 0) -
    -
    {{ trans('entities.shelves') }}
    - @include('entities.list', ['entities' => $bookParentShelves, 'style' => 'compact']) -
    - @endif - - @if(count($activity) > 0) -
    -
    {{ trans('entities.recent_activity') }}
    - @include('common.activity-list', ['activity' => $activity]) -
    - @endif + @include('books.parts.show-sidebar-section-tags', ['book' => $book]) + @include('books.parts.show-sidebar-section-shelves', ['bookParentShelves' => $bookParentShelves]) + @include('books.parts.show-sidebar-section-activity', ['activity' => $activity]) @stop diff --git a/resources/views/chapters/parts/show-sidebar-section-actions.blade.php b/resources/views/chapters/parts/show-sidebar-section-actions.blade.php new file mode 100644 index 00000000000..55df999a22c --- /dev/null +++ b/resources/views/chapters/parts/show-sidebar-section-actions.blade.php @@ -0,0 +1,65 @@ +
    +
    {{ trans('common.actions') }}
    + +
    \ No newline at end of file diff --git a/resources/views/chapters/parts/show-sidebar-section-details.blade.php b/resources/views/chapters/parts/show-sidebar-section-details.blade.php new file mode 100644 index 00000000000..a424b8d3fac --- /dev/null +++ b/resources/views/chapters/parts/show-sidebar-section-details.blade.php @@ -0,0 +1,38 @@ +
    +
    {{ trans('common.details') }}
    + +
    \ No newline at end of file diff --git a/resources/views/chapters/parts/show-sidebar-section-tags.blade.php b/resources/views/chapters/parts/show-sidebar-section-tags.blade.php new file mode 100644 index 00000000000..d28ff63833e --- /dev/null +++ b/resources/views/chapters/parts/show-sidebar-section-tags.blade.php @@ -0,0 +1,5 @@ +@if($chapter->tags->count() > 0) +
    + @include('entities.tag-list', ['entity' => $chapter]) +
    +@endif \ No newline at end of file diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index 585bf8a3b8d..7ef877661c0 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -63,123 +63,13 @@ @stop @section('right') - -
    -
    {{ trans('common.details') }}
    - -
    - -
    -
    {{ trans('common.actions') }}
    - -
    + @include('chapters.parts.show-sidebar-section-details', ['chapter' => $chapter, 'book' => $book, 'watchOptions' => $watchOptions]) + @include('chapters.parts.show-sidebar-section-actions', ['chapter' => $chapter, 'watchOptions' => $watchOptions]) @stop @section('left') - @include('entities.search-form', ['label' => trans('entities.chapters_search_this')]) - - @if($chapter->tags->count() > 0) -
    - @include('entities.tag-list', ['entity' => $chapter]) -
    - @endif - + @include('chapters.parts.show-sidebar-section-tags', ['chapter' => $chapter]) @include('entities.book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree]) @stop diff --git a/resources/views/pages/parts/show-sidebar-section-actions.blade.php b/resources/views/pages/parts/show-sidebar-section-actions.blade.php new file mode 100644 index 00000000000..ae115b69e23 --- /dev/null +++ b/resources/views/pages/parts/show-sidebar-section-actions.blade.php @@ -0,0 +1,57 @@ +
    +
    {{ trans('common.actions') }}
    + + + +
    \ No newline at end of file diff --git a/resources/views/pages/parts/show-sidebar-section-attachments.blade.php b/resources/views/pages/parts/show-sidebar-section-attachments.blade.php new file mode 100644 index 00000000000..9757240159b --- /dev/null +++ b/resources/views/pages/parts/show-sidebar-section-attachments.blade.php @@ -0,0 +1,8 @@ +@if($page->attachments->count() > 0) +
    +
    {{ trans('entities.pages_attachments') }}
    +
    + @include('attachments.list', ['attachments' => $page->attachments]) +
    +
    +@endif \ No newline at end of file diff --git a/resources/views/pages/parts/show-sidebar-section-details.blade.php b/resources/views/pages/parts/show-sidebar-section-details.blade.php new file mode 100644 index 00000000000..391f30ce4cc --- /dev/null +++ b/resources/views/pages/parts/show-sidebar-section-details.blade.php @@ -0,0 +1,61 @@ +
    +
    {{ trans('common.details') }}
    + +
    \ No newline at end of file diff --git a/resources/views/pages/parts/show-sidebar-section-page-nav.blade.php b/resources/views/pages/parts/show-sidebar-section-page-nav.blade.php new file mode 100644 index 00000000000..88db87e6483 --- /dev/null +++ b/resources/views/pages/parts/show-sidebar-section-page-nav.blade.php @@ -0,0 +1,15 @@ +@if(isset($pageNav) && count($pageNav)) + +@endif \ No newline at end of file diff --git a/resources/views/pages/parts/show-sidebar-section-tags.blade.php b/resources/views/pages/parts/show-sidebar-section-tags.blade.php new file mode 100644 index 00000000000..354da627c55 --- /dev/null +++ b/resources/views/pages/parts/show-sidebar-section-tags.blade.php @@ -0,0 +1,5 @@ +@if($page->tags->count() > 0) +
    + @include('entities.tag-list', ['entity' => $page]) +
    +@endif \ No newline at end of file diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php index 3338646a509..d34bfc6aee5 100644 --- a/resources/views/pages/show.blade.php +++ b/resources/views/pages/show.blade.php @@ -36,159 +36,13 @@ class="page-content clearfix"> @stop @section('left') - - @if($page->tags->count() > 0) -
    - @include('entities.tag-list', ['entity' => $page]) -
    - @endif - - @if ($page->attachments->count() > 0) -
    -
    {{ trans('entities.pages_attachments') }}
    -
    - @include('attachments.list', ['attachments' => $page->attachments]) -
    -
    - @endif - - @if (isset($pageNav) && count($pageNav)) - - @endif - + @include('pages.parts.show-sidebar-section-tags', ['page' => $page]) + @include('pages.parts.show-sidebar-section-attachments', ['page' => $page]) + @include('pages.parts.show-sidebar-section-page-nav', ['pageNav' => $pageNav]) @include('entities.book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree]) @stop @section('right') -
    -
    {{ trans('common.details') }}
    - -
    - -
    -
    {{ trans('common.actions') }}
    - - - -
    + @include('pages.parts.show-sidebar-section-details', ['page' => $page, 'watchOptions' => $watchOptions, 'book' => $book]) + @include('pages.parts.show-sidebar-section-actions', ['page' => $page, 'watchOptions' => $watchOptions]) @stop diff --git a/resources/views/shelves/index.blade.php b/resources/views/shelves/index.blade.php index bb7c57e0fde..70357068d7e 100644 --- a/resources/views/shelves/index.blade.php +++ b/resources/views/shelves/index.blade.php @@ -5,51 +5,11 @@ @stop @section('right') - -
    -
    {{ trans('common.actions') }}
    - -
    - + @include('shelves.parts.index-sidebar-section-actions', ['view' => $view]) @stop @section('left') - @if($recents) -
    -
    {{ trans('entities.recently_viewed') }}
    - @include('entities.list', ['entities' => $recents, 'style' => 'compact']) -
    - @endif - - - -
    -
    {{ trans('entities.shelves_new') }}
    - @if(count($new) > 0) - @include('entities.list', ['entities' => $new, 'style' => 'compact']) - @else -

    {{ trans('entities.shelves_new_empty') }}

    - @endif -
    + @include('shelves.parts.index-sidebar-section-recents', ['recents' => $recents]) + @include('shelves.parts.index-sidebar-section-popular', ['popular' => $popular]) + @include('shelves.parts.index-sidebar-section-new', ['new' => $new]) @stop \ No newline at end of file diff --git a/resources/views/shelves/parts/index-sidebar-section-actions.blade.php b/resources/views/shelves/parts/index-sidebar-section-actions.blade.php new file mode 100644 index 00000000000..d5cdb4056a4 --- /dev/null +++ b/resources/views/shelves/parts/index-sidebar-section-actions.blade.php @@ -0,0 +1,18 @@ +
    +
    {{ trans('common.actions') }}
    + +
    \ No newline at end of file diff --git a/resources/views/shelves/parts/index-sidebar-section-new.blade.php b/resources/views/shelves/parts/index-sidebar-section-new.blade.php new file mode 100644 index 00000000000..602f60ebeaa --- /dev/null +++ b/resources/views/shelves/parts/index-sidebar-section-new.blade.php @@ -0,0 +1,8 @@ +
    +
    {{ trans('entities.shelves_new') }}
    + @if(count($new) > 0) + @include('entities.list', ['entities' => $new, 'style' => 'compact']) + @else +

    {{ trans('entities.shelves_new_empty') }}

    + @endif +
    \ No newline at end of file diff --git a/resources/views/shelves/parts/index-sidebar-section-popular.blade.php b/resources/views/shelves/parts/index-sidebar-section-popular.blade.php new file mode 100644 index 00000000000..956321c5e01 --- /dev/null +++ b/resources/views/shelves/parts/index-sidebar-section-popular.blade.php @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/resources/views/shelves/parts/index-sidebar-section-recents.blade.php b/resources/views/shelves/parts/index-sidebar-section-recents.blade.php new file mode 100644 index 00000000000..f1a68ba4f99 --- /dev/null +++ b/resources/views/shelves/parts/index-sidebar-section-recents.blade.php @@ -0,0 +1,6 @@ +@if($recents) +
    +
    {{ trans('entities.recently_viewed') }}
    + @include('entities.list', ['entities' => $recents, 'style' => 'compact']) +
    +@endif \ No newline at end of file diff --git a/resources/views/shelves/parts/show-sidebar-section-actions.blade.php b/resources/views/shelves/parts/show-sidebar-section-actions.blade.php new file mode 100644 index 00000000000..ba92e5f703b --- /dev/null +++ b/resources/views/shelves/parts/show-sidebar-section-actions.blade.php @@ -0,0 +1,43 @@ +
    +
    {{ trans('common.actions') }}
    + +
    \ No newline at end of file diff --git a/resources/views/shelves/parts/show-sidebar-section-activity.blade.php b/resources/views/shelves/parts/show-sidebar-section-activity.blade.php new file mode 100644 index 00000000000..c1c5c1d3ecf --- /dev/null +++ b/resources/views/shelves/parts/show-sidebar-section-activity.blade.php @@ -0,0 +1,6 @@ +@if(count($activity) > 0) +
    +
    {{ trans('entities.recent_activity') }}
    + @include('common.activity-list', ['activity' => $activity]) +
    +@endif \ No newline at end of file diff --git a/resources/views/shelves/parts/show-sidebar-section-details.blade.php b/resources/views/shelves/parts/show-sidebar-section-details.blade.php new file mode 100644 index 00000000000..8933cc41973 --- /dev/null +++ b/resources/views/shelves/parts/show-sidebar-section-details.blade.php @@ -0,0 +1,21 @@ +
    +
    {{ trans('common.details') }}
    + +
    \ No newline at end of file diff --git a/resources/views/shelves/parts/show-sidebar-section-tags.blade.php b/resources/views/shelves/parts/show-sidebar-section-tags.blade.php new file mode 100644 index 00000000000..265d61cd0d3 --- /dev/null +++ b/resources/views/shelves/parts/show-sidebar-section-tags.blade.php @@ -0,0 +1,5 @@ +@if($shelf->tags->count() > 0) +
    + @include('entities.tag-list', ['entity' => $shelf]) +
    +@endif \ No newline at end of file diff --git a/resources/views/shelves/show.blade.php b/resources/views/shelves/show.blade.php index 9ee14f1bf4e..9d07e5da018 100644 --- a/resources/views/shelves/show.blade.php +++ b/resources/views/shelves/show.blade.php @@ -69,87 +69,13 @@ @stop @section('left') - - @if($shelf->tags->count() > 0) -
    - @include('entities.tag-list', ['entity' => $shelf]) -
    - @endif - -
    -
    {{ trans('common.details') }}
    - -
    - - @if(count($activity) > 0) -
    -
    {{ trans('entities.recent_activity') }}
    - @include('common.activity-list', ['activity' => $activity]) -
    - @endif + @include('shelves.parts.show-sidebar-section-tags', ['shelf' => $shelf]) + @include('shelves.parts.show-sidebar-section-details', ['shelf' => $shelf]) + @include('shelves.parts.show-sidebar-section-activity', ['activity' => $activity]) @stop @section('right') -
    -
    {{ trans('common.actions') }}
    - -
    + @include('shelves.parts.show-sidebar-section-actions', ['shelf' => $shelf, 'view' => $view]) @stop From 10ebe53bd9c9f086efaa35eecd3656f1a201d84d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 13 Feb 2026 14:14:28 +0000 Subject: [PATCH 044/204] Page Content: Added more complex & configurable content filtering - Added new option to control parts of the filter. - Added whitelist filtering pass via HTMLPurifier. --- app/Activity/Models/Comment.php | 4 +- app/Config/app.php | 12 ++ app/Entities/Tools/EntityHtmlDescription.php | 4 +- app/Entities/Tools/PageContent.php | 24 +++- app/Theming/CustomHtmlHeadContentProvider.php | 22 +-- app/Util/HtmlContentFilter.php | 129 ++++++++++++------ app/Util/HtmlContentFilterConfig.php | 31 +++++ composer.json | 4 +- composer.lock | 123 ++++++++++++++++- storage/purifier/.gitignore | 2 + 10 files changed, 292 insertions(+), 63 deletions(-) create mode 100644 app/Util/HtmlContentFilterConfig.php create mode 100644 storage/purifier/.gitignore diff --git a/app/Activity/Models/Comment.php b/app/Activity/Models/Comment.php index ce05e3df35b..ab7d917729c 100644 --- a/app/Activity/Models/Comment.php +++ b/app/Activity/Models/Comment.php @@ -8,6 +8,7 @@ use BookStack\Users\Models\HasCreatorAndUpdater; use BookStack\Users\Models\OwnableInterface; use BookStack\Util\HtmlContentFilter; +use BookStack\Util\HtmlContentFilterConfig; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -82,7 +83,8 @@ public function logDescriptor(): string public function safeHtml(): string { - return HtmlContentFilter::removeActiveContentFromHtmlString($this->html ?? ''); + $filter = new HtmlContentFilter(new HtmlContentFilterConfig()); + return $filter->filterString($this->html ?? ''); } public function jointPermissions(): HasMany diff --git a/app/Config/app.php b/app/Config/app.php index 40e542d3e16..acd27e98c02 100644 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -42,6 +42,18 @@ // Even when overridden the WYSIWYG editor may still escape script content. 'allow_content_scripts' => env('ALLOW_CONTENT_SCRIPTS', false), + // Control the behaviour of page content filtering. + // This setting is a collection of characters which represent different available filters: + // - j - Filter out JavaScript based content + // - h - Filter out unexpected, potentially dangerous, HTML elements + // - f - Filter out unexpected form elements + // - a - Run content through a more complex allow-list filter + // This defaults to using all filters, unless ALLOW_CONTENT_SCRIPTS is set to true in which case no filters are used. + // Note: These filters are a best attempt, and may not be 100% effective. They are typically a layer used in addition to other security measures. + // TODO - Add to example env + // TODO - Remove allow_content_scripts option above + 'content_filtering' => env('CONTENT_FILTERING', env('ALLOW_CONTENT_SCRIPTS', false) === true ? '' : 'jfha'), + // Allow server-side fetches to be performed to potentially unknown // and user-provided locations. Primarily used in exports when loading // in externally referenced assets. diff --git a/app/Entities/Tools/EntityHtmlDescription.php b/app/Entities/Tools/EntityHtmlDescription.php index b14deb257a7..6bbfb9b6651 100644 --- a/app/Entities/Tools/EntityHtmlDescription.php +++ b/app/Entities/Tools/EntityHtmlDescription.php @@ -6,6 +6,7 @@ use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Util\HtmlContentFilter; +use BookStack\Util\HtmlContentFilterConfig; class EntityHtmlDescription { @@ -50,7 +51,8 @@ public function getHtml(bool $raw = false): string return $html; } - return HtmlContentFilter::removeActiveContentFromHtmlString($html); + $filter = new HtmlContentFilter(new HtmlContentFilterConfig()); + return $filter->filterString($html); } public function getPlain(): string diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index 5358e8f0c5b..ca06e696185 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -13,6 +13,7 @@ use BookStack\Uploads\ImageService; use BookStack\Users\Models\User; use BookStack\Util\HtmlContentFilter; +use BookStack\Util\HtmlContentFilterConfig; use BookStack\Util\HtmlDocument; use BookStack\Util\WebSafeMimeSniffer; use Closure; @@ -317,11 +318,28 @@ public function render(bool $blankIncludes = false): string $this->updateIdsRecursively($doc->getBody(), 0, $idMap, $changeMap); } - if (!config('app.allow_content_scripts')) { - HtmlContentFilter::removeActiveContentFromDocument($doc); + $cacheKey = $this->getContentCacheKey($doc->getBodyInnerHtml()); + $cached = cache()->get($cacheKey, null); + if ($cached !== null) { + return $cached; } - return $doc->getBodyInnerHtml(); + $filterConfig = HtmlContentFilterConfig::fromConfigString(config('app.content_filtering')); + $filter = new HtmlContentFilter($filterConfig); + $filtered = $filter->filterDocument($doc); + + $cacheTime = 86400 * 7; // 1 week + cache()->put($cacheKey, $filtered, $cacheTime); + + return $filtered; + } + + protected function getContentCacheKey(string $html): string + { + $contentHash = md5($html); + $contentId = $this->page->id; + $contentTime = $this->page->updated_at->timestamp; + return "page-content-cache::{$contentId}::{$contentTime}::{$contentHash}"; } /** diff --git a/app/Theming/CustomHtmlHeadContentProvider.php b/app/Theming/CustomHtmlHeadContentProvider.php index e0cf5b3b5c7..dab30606c34 100644 --- a/app/Theming/CustomHtmlHeadContentProvider.php +++ b/app/Theming/CustomHtmlHeadContentProvider.php @@ -4,25 +4,16 @@ use BookStack\Util\CspService; use BookStack\Util\HtmlContentFilter; +use BookStack\Util\HtmlContentFilterConfig; use BookStack\Util\HtmlNonceApplicator; use Illuminate\Contracts\Cache\Repository as Cache; class CustomHtmlHeadContentProvider { - /** - * @var CspService - */ - protected $cspService; - - /** - * @var Cache - */ - protected $cache; - - public function __construct(CspService $cspService, Cache $cache) - { - $this->cspService = $cspService; - $this->cache = $cache; + public function __construct( + protected CspService $cspService, + protected Cache $cache + ) { } /** @@ -50,7 +41,8 @@ public function forExport(): string $hash = md5($content); return $this->cache->remember('custom-head-export:' . $hash, 86400, function () use ($content) { - return HtmlContentFilter::removeActiveContentFromHtmlString($content); + $config = new HtmlContentFilterConfig(filterOutNonContentElements: false); + return (new HtmlContentFilter($config))->filterString($content); }); } diff --git a/app/Util/HtmlContentFilter.php b/app/Util/HtmlContentFilter.php index ad5bf8c5fd3..842e4246736 100644 --- a/app/Util/HtmlContentFilter.php +++ b/app/Util/HtmlContentFilter.php @@ -5,15 +5,53 @@ use DOMAttr; use DOMElement; use DOMNodeList; +use HTMLPurifier; +use HTMLPurifier_HTML5Config; class HtmlContentFilter { - /** - * Remove all active content from the given HTML document. - * This aims to cover anything which can dynamically deal with, or send, data - * like any JavaScript actions or form content. - */ - public static function removeActiveContentFromDocument(HtmlDocument $doc): void + public function __construct( + protected HtmlContentFilterConfig $config + ) { + } + + public function filterDocument(HtmlDocument $doc): string + { + if ($this->config->filterOutJavaScript) { + $this->filterOutScriptsFromDocument($doc); + } + if ($this->config->filterOutFormElements) { + $this->filterOutFormElementsFromDocument($doc); + } + if ($this->config->filterOutBadHtmlElements) { + $this->filterOutBadHtmlElementsFromDocument($doc); + } + if ($this->config->filterOutNonContentElements) { + $this->filterOutNonContentElementsFromDocument($doc); + } + + $filtered = $doc->getBodyInnerHtml(); + if ($this->config->useAllowListFilter) { + $filtered = $this->applyAllowListFiltering($filtered); + } + + return $filtered; + } + + public function filterString(string $html): string + { + return $this->filterDocument(new HtmlDocument($html)); + } + + protected function applyAllowListFiltering(string $html): string + { + $config = HTMLPurifier_HTML5Config::createDefault(); + $config->set('Cache.SerializerPath', storage_path('purifier')); + $purifier = new HTMLPurifier($config); + return $purifier->purify($html); + } + + protected function filterOutScriptsFromDocument(HtmlDocument $doc): void { // Remove standard script tags $scriptElems = $doc->queryXPath('//script'); @@ -27,10 +65,6 @@ public static function removeActiveContentFromDocument(HtmlDocument $doc): void $badForms = $doc->queryXPath('//*[' . static::xpathContains('@action', 'javascript:') . '] | //*[' . static::xpathContains('@formaction', 'javascript:') . ']'); static::removeNodes($badForms); - // Remove meta tag to prevent external redirects - $metaTags = $doc->queryXPath('//meta[' . static::xpathContains('@content', 'url') . ']'); - static::removeNodes($metaTags); - // Remove data or JavaScript iFrames $badIframes = $doc->queryXPath('//*[' . static::xpathContains('@src', 'data:') . '] | //*[' . static::xpathContains('@src', 'javascript:') . '] | //*[@srcdoc]'); static::removeNodes($badIframes); @@ -49,7 +83,10 @@ public static function removeActiveContentFromDocument(HtmlDocument $doc): void // Remove 'on*' attributes $onAttributes = $doc->queryXPath('//@*[starts-with(name(), \'on\')]'); static::removeAttributes($onAttributes); + } + protected function filterOutFormElementsFromDocument(HtmlDocument $doc): void + { // Remove form elements $formElements = ['form', 'fieldset', 'button', 'textarea', 'select']; foreach ($formElements as $formElement) { @@ -75,41 +112,21 @@ public static function removeActiveContentFromDocument(HtmlDocument $doc): void } } - /** - * Remove active content from the given HTML string. - * This aims to cover anything which can dynamically deal with, or send, data - * like any JavaScript actions or form content. - */ - public static function removeActiveContentFromHtmlString(string $html): string + protected function filterOutBadHtmlElementsFromDocument(HtmlDocument $doc): void { - if (empty($html)) { - return $html; - } - - $doc = new HtmlDocument($html); - static::removeActiveContentFromDocument($doc); - - return $doc->getBodyInnerHtml(); - } - - /** - * Alias using the old method name to avoid potential compatibility breaks during patch release. - * To remove in future feature release. - * @deprecated Use removeActiveContentFromDocument instead. - */ - public static function removeScriptsFromDocument(HtmlDocument $doc): void - { - static::removeActiveContentFromDocument($doc); + // Remove meta tag to prevent external redirects + $metaTags = $doc->queryXPath('//meta[' . static::xpathContains('@content', 'url') . ']'); + static::removeNodes($metaTags); } - /** - * Alias using the old method name to avoid potential compatibility breaks during patch release. - * To remove in future feature release. - * @deprecated Use removeActiveContentFromHtmlString instead. - */ - public static function removeScriptsFromHtmlString(string $html): string + protected function filterOutNonContentElementsFromDocument(HtmlDocument $doc): void { - return static::removeActiveContentFromHtmlString($html); + // Remove non-content elements + $formElements = ['link', 'style', 'meta', 'title', 'template']; + foreach ($formElements as $formElement) { + $matchingFormElements = $doc->queryXPath('//' . $formElement); + static::removeNodes($matchingFormElements); + } } /** @@ -147,4 +164,34 @@ protected static function removeAttributes(DOMNodeList $attrs): void $parentNode->removeAttribute($attrName); } } + + /** + * Alias using the old method name to avoid potential compatibility breaks during patch release. + * To remove in future feature release. + * @deprecated Use filterDocument instead. + */ + public static function removeScriptsFromDocument(HtmlDocument $doc): void + { + $config = new HtmlContentFilterConfig( + filterOutNonContentElements: false, + useAllowListFilter: false, + ); + $filter = new static($config); + $filter->filterDocument($doc); + } + + /** + * Alias using the old method name to avoid potential compatibility breaks during patch release. + * To remove in future feature release. + * @deprecated Use filterString instead. + */ + public static function removeScriptsFromHtmlString(string $html): string + { + $config = new HtmlContentFilterConfig( + filterOutNonContentElements: false, + useAllowListFilter: false, + ); + $filter = new static($config); + return $filter->filterString($html); + } } diff --git a/app/Util/HtmlContentFilterConfig.php b/app/Util/HtmlContentFilterConfig.php new file mode 100644 index 00000000000..2cb77ea5815 --- /dev/null +++ b/app/Util/HtmlContentFilterConfig.php @@ -0,0 +1,31 @@ +=5.3" + }, + "require-dev": { + "masterminds/html5": "^2.7", + "php-coveralls/php-coveralls": "^1.1|^2.1", + "phpunit/phpunit": ">=4.7 <10.0" + }, + "suggest": { + "masterminds/html5": "Required to use HTMLPurifier_Lexer_HTML5" + }, + "type": "library", + "autoload": { + "classmap": [ + "library/HTMLPurifier/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "xemlock", + "email": "xemlock@gmail.com" + } + ], + "description": "HTML5 support for HTML Purifier", + "homepage": "https://github.com/xemlock/htmlpurifier-html5", + "keywords": [ + "HTML5", + "Purifier", + "html", + "htmlpurifier", + "security", + "tidy", + "validator", + "xss" + ], + "support": { + "issues": "https://github.com/xemlock/htmlpurifier-html5/issues", + "source": "https://github.com/xemlock/htmlpurifier-html5/tree/v0.1.12" + }, + "time": "2026-02-09T21:03:14+00:00" } ], "packages-dev": [ diff --git a/storage/purifier/.gitignore b/storage/purifier/.gitignore new file mode 100644 index 00000000000..c96a04f008e --- /dev/null +++ b/storage/purifier/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file From 0f040fe8b1bbaaee4d088262aa4482a4b19b1c46 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Feb 2026 16:17:03 +0000 Subject: [PATCH 045/204] Content: Tuned HTML purifier for our use Tested it with a range of supported, including uncommon, content types and added support, or changed config, where needed. Been through docs for all HTMLPurifier options to assess what's relevant. --- app/Entities/Tools/PageContent.php | 3 +- app/Util/ConfiguredHtmlPurifier.php | 101 ++++++++++++++++++++++++++++ app/Util/HtmlContentFilter.php | 6 +- 3 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 app/Util/ConfiguredHtmlPurifier.php diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index ca06e696185..67c6e4cf6e8 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -321,12 +321,13 @@ public function render(bool $blankIncludes = false): string $cacheKey = $this->getContentCacheKey($doc->getBodyInnerHtml()); $cached = cache()->get($cacheKey, null); if ($cached !== null) { - return $cached; +// return $cached; } $filterConfig = HtmlContentFilterConfig::fromConfigString(config('app.content_filtering')); $filter = new HtmlContentFilter($filterConfig); $filtered = $filter->filterDocument($doc); +// $filtered = $doc->getBodyInnerHtml(); $cacheTime = 86400 * 7; // 1 week cache()->put($cacheKey, $filtered, $cacheTime); diff --git a/app/Util/ConfiguredHtmlPurifier.php b/app/Util/ConfiguredHtmlPurifier.php new file mode 100644 index 00000000000..5aab25b4745 --- /dev/null +++ b/app/Util/ConfiguredHtmlPurifier.php @@ -0,0 +1,101 @@ +setConfig($config); + + $htmlDef = $config->getDefinition('HTML', true, true); + if ($htmlDef instanceof HTMLPurifier_HTMLDefinition) { + $this->configureDefinition($htmlDef); + } + + $this->purifier = new HTMLPurifier($config); + } + + protected function setConfig(HTMLPurifier_Config $config): void + { + $config->set('Cache.SerializerPath', storage_path('purifier')); + $config->set('CSS.AllowTricky', true); + $config->set('HTML.SafeIframe', true); + $config->set('Attr.EnableID', true); + $config->set('Attr.ID.HTML5', true); + $config->set('Output.FixInnerHTML', false); + $config->set('URI.SafeIframeRegexp', '%^(http://|https://)%'); + $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 + } + + public function configureDefinition(HTMLPurifier_HTMLDefinition $definition): void + { + // Allow the object element + $definition->addElement( + 'object', + 'Inline', + 'Flow', + 'Common', + [ + 'data' => 'URI', + 'type' => 'Text', + 'width' => 'Length', + 'height' => 'Length', + ] + ); + + // Allow the embed element + $definition->addElement( + 'embed', + 'Inline', + 'Empty', + 'Common', + [ + 'src' => 'URI', + 'type' => 'Text', + 'width' => 'Length', + 'height' => 'Length', + ] + ); + + // Allow checkbox inputs + $definition->addElement( + 'input', + 'Formctrl', + 'Empty', + 'Common', + [ + 'checked' => 'Bool#checked', + 'disabled' => 'Bool#disabled', + 'name' => 'Text', + 'readonly' => 'Bool#readonly', + 'type' => 'Enum#checkbox', + 'value' => 'Text', + ] + ); + } + + public function purify(string $html): string + { + return $this->purifier->purify($html); + } +} diff --git a/app/Util/HtmlContentFilter.php b/app/Util/HtmlContentFilter.php index 842e4246736..79b1cdc93c4 100644 --- a/app/Util/HtmlContentFilter.php +++ b/app/Util/HtmlContentFilter.php @@ -5,8 +5,6 @@ use DOMAttr; use DOMElement; use DOMNodeList; -use HTMLPurifier; -use HTMLPurifier_HTML5Config; class HtmlContentFilter { @@ -45,9 +43,7 @@ public function filterString(string $html): string protected function applyAllowListFiltering(string $html): string { - $config = HTMLPurifier_HTML5Config::createDefault(); - $config->set('Cache.SerializerPath', storage_path('purifier')); - $purifier = new HTMLPurifier($config); + $purifier = new ConfiguredHtmlPurifier(); return $purifier->purify($html); } From 227027fc4570270395fe5dd0aa2bb8201163752a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Feb 2026 16:46:09 +0000 Subject: [PATCH 046/204] Content: Updated purifier and content caching - Updated page content cache to use app version in cache key - Moved purifier cache into framework to better work with existing expected folders. - Added app version check to purifier so that it will reset its own cache on app version change. --- app/Entities/Tools/PageContent.php | 7 +++-- app/Util/ConfiguredHtmlPurifier.php | 34 +++++++++++++++++++-- storage/{ => framework}/purifier/.gitignore | 0 3 files changed, 36 insertions(+), 5 deletions(-) rename storage/{ => framework}/purifier/.gitignore (100%) diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index 67c6e4cf6e8..436c4f0bed8 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -2,6 +2,7 @@ namespace BookStack\Entities\Tools; +use BookStack\App\AppVersion; use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Tools\Markdown\MarkdownToHtml; @@ -321,13 +322,12 @@ public function render(bool $blankIncludes = false): string $cacheKey = $this->getContentCacheKey($doc->getBodyInnerHtml()); $cached = cache()->get($cacheKey, null); if ($cached !== null) { -// return $cached; + return $cached; } $filterConfig = HtmlContentFilterConfig::fromConfigString(config('app.content_filtering')); $filter = new HtmlContentFilter($filterConfig); $filtered = $filter->filterDocument($doc); -// $filtered = $doc->getBodyInnerHtml(); $cacheTime = 86400 * 7; // 1 week cache()->put($cacheKey, $filtered, $cacheTime); @@ -340,7 +340,8 @@ protected function getContentCacheKey(string $html): string $contentHash = md5($html); $contentId = $this->page->id; $contentTime = $this->page->updated_at->timestamp; - return "page-content-cache::{$contentId}::{$contentTime}::{$contentHash}"; + $appVersion = AppVersion::get(); + return "page-content-cache::{$appVersion}::{$contentId}::{$contentTime}::{$contentHash}"; } /** diff --git a/app/Util/ConfiguredHtmlPurifier.php b/app/Util/ConfiguredHtmlPurifier.php index 5aab25b4745..d63d2ad5f3c 100644 --- a/app/Util/ConfiguredHtmlPurifier.php +++ b/app/Util/ConfiguredHtmlPurifier.php @@ -2,19 +2,29 @@ namespace BookStack\Util; +use BookStack\App\AppVersion; use HTMLPurifier; use HTMLPurifier_Config; +use HTMLPurifier_DefinitionCache_Serializer; use HTMLPurifier_HTML5Config; use HTMLPurifier_HTMLDefinition; +/** + * Provides a configured HTML Purifier instance. + * https://github.com/ezyang/htmlpurifier + * Also uses this to extend support to HTML5 elements: + * https://github.com/xemlock/htmlpurifier-html5 + */ class ConfiguredHtmlPurifier { protected HTMLPurifier $purifier; + protected static bool $cachedChecked = false; public function __construct() { $config = HTMLPurifier_HTML5Config::createDefault(); $this->setConfig($config); + $this->resetCacheIfNeeded($config); $htmlDef = $config->getDefinition('HTML', true, true); if ($htmlDef instanceof HTMLPurifier_HTMLDefinition) { @@ -24,9 +34,29 @@ public function __construct() $this->purifier = new HTMLPurifier($config); } + protected function resetCacheIfNeeded(HTMLPurifier_Config $config): void + { + if (self::$cachedChecked) { + return; + } + + $cachedForVersion = cache('htmlpurifier::cache-version'); + $appVersion = AppVersion::get(); + if ($cachedForVersion !== $appVersion) { + foreach (['HTML', 'CSS', 'URI'] as $name) { + $cache = new HTMLPurifier_DefinitionCache_Serializer($name); + $cache->flush($config); + } + cache()->set('htmlpurifier::cache-version', $appVersion); + } + + self::$cachedChecked = true; + } + protected function setConfig(HTMLPurifier_Config $config): void { - $config->set('Cache.SerializerPath', storage_path('purifier')); + $config->set('Cache.SerializerPath', storage_path('framework/purifier')); + $config->set('Core.AllowHostnameUnderscore', true); $config->set('CSS.AllowTricky', true); $config->set('HTML.SafeIframe', true); $config->set('Attr.EnableID', true); @@ -44,7 +74,7 @@ protected function setConfig(HTMLPurifier_Config $config): void 'file' => true, ]); - $config->set('Cache.DefinitionImpl', null); // Disable cache during testing + // $config->set('Cache.DefinitionImpl', null); // Disable cache during testing } public function configureDefinition(HTMLPurifier_HTMLDefinition $definition): void diff --git a/storage/purifier/.gitignore b/storage/framework/purifier/.gitignore similarity index 100% rename from storage/purifier/.gitignore rename to storage/framework/purifier/.gitignore From 035be66ebc7d4a312b5a240283a5a13da9694779 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Feb 2026 18:44:14 +0000 Subject: [PATCH 047/204] Content: Updated tests and CSP usage of content script setting Updates CSP to use new content_filtering option. Splits out content filtering tests to their own class. Updated tests where needed to adapt to changes. --- app/Entities/Tools/PageContent.php | 2 +- app/Theming/CustomHtmlHeadContentProvider.php | 2 +- app/Util/ConfiguredHtmlPurifier.php | 2 +- app/Util/CspService.php | 9 +- tests/Entity/PageContentFilteringTest.php | 353 ++++++++++++++++++ tests/Entity/PageContentTest.php | 348 +---------------- tests/SecurityHeaderTest.php | 6 +- 7 files changed, 368 insertions(+), 354 deletions(-) create mode 100644 tests/Entity/PageContentFilteringTest.php diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index 436c4f0bed8..f8a0617395b 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -339,7 +339,7 @@ protected function getContentCacheKey(string $html): string { $contentHash = md5($html); $contentId = $this->page->id; - $contentTime = $this->page->updated_at->timestamp; + $contentTime = $this->page->updated_at?->timestamp ?? time(); $appVersion = AppVersion::get(); return "page-content-cache::{$appVersion}::{$contentId}::{$contentTime}::{$contentHash}"; } diff --git a/app/Theming/CustomHtmlHeadContentProvider.php b/app/Theming/CustomHtmlHeadContentProvider.php index dab30606c34..9f794a077ba 100644 --- a/app/Theming/CustomHtmlHeadContentProvider.php +++ b/app/Theming/CustomHtmlHeadContentProvider.php @@ -41,7 +41,7 @@ public function forExport(): string $hash = md5($content); return $this->cache->remember('custom-head-export:' . $hash, 86400, function () use ($content) { - $config = new HtmlContentFilterConfig(filterOutNonContentElements: false); + $config = new HtmlContentFilterConfig(filterOutNonContentElements: false, useAllowListFilter: false); return (new HtmlContentFilter($config))->filterString($content); }); } diff --git a/app/Util/ConfiguredHtmlPurifier.php b/app/Util/ConfiguredHtmlPurifier.php index d63d2ad5f3c..014b2a3bf2b 100644 --- a/app/Util/ConfiguredHtmlPurifier.php +++ b/app/Util/ConfiguredHtmlPurifier.php @@ -62,7 +62,7 @@ protected function setConfig(HTMLPurifier_Config $config): void $config->set('Attr.EnableID', true); $config->set('Attr.ID.HTML5', true); $config->set('Output.FixInnerHTML', false); - $config->set('URI.SafeIframeRegexp', '%^(http://|https://)%'); + $config->set('URI.SafeIframeRegexp', '%^(http://|https://|//)%'); $config->set('URI.AllowedSchemes', [ 'http' => true, 'https' => true, diff --git a/app/Util/CspService.php b/app/Util/CspService.php index 4262b5c98f8..466acb49148 100644 --- a/app/Util/CspService.php +++ b/app/Util/CspService.php @@ -65,7 +65,7 @@ public function allowedIFrameHostsConfigured(): bool */ protected function getScriptSrc(): string { - if (config('app.allow_content_scripts')) { + if ($this->scriptFilteringDisabled()) { return ''; } @@ -108,7 +108,7 @@ protected function getFrameSrc(): string */ protected function getObjectSrc(): string { - if (config('app.allow_content_scripts')) { + if ($this->scriptFilteringDisabled()) { return ''; } @@ -124,6 +124,11 @@ protected function getBaseUri(): string return "base-uri 'self'"; } + protected function scriptFilteringDisabled(): bool + { + return !HtmlContentFilterConfig::fromConfigString(config('app.content_filtering'))->filterOutJavaScript; + } + protected function getAllowedIframeHosts(): array { $hosts = config('app.iframe_hosts') ?? ''; diff --git a/tests/Entity/PageContentFilteringTest.php b/tests/Entity/PageContentFilteringTest.php new file mode 100644 index 00000000000..e1295034d68 --- /dev/null +++ b/tests/Entity/PageContentFilteringTest.php @@ -0,0 +1,353 @@ +asEditor(); + $page = $this->entities->page(); + $script = 'abc123abc123'; + $page->html = "escape {$script}"; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $pageView->assertDontSee($script, false); + $pageView->assertSee('abc123abc123'); + } + + public function test_more_complex_content_script_escaping_scenarios() + { + $checks = [ + "

    Some script

    ", + "

    Some script

    ", + "

    Some script

    ", + "

    Some script

    ", + "

    Some script

    ", + "

    Some script

    ", + ]; + + $this->asEditor(); + $page = $this->entities->page(); + + foreach ($checks as $check) { + $page->html = $check; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $this->withHtml($pageView)->assertElementNotContains('.page-content', ''); + } + } + + public function test_js_and_base64_src_urls_are_removed() + { + $checks = [ + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + ]; + + $this->asEditor(); + $page = $this->entities->page(); + + foreach ($checks as $check) { + $page->html = $check; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $html = $this->withHtml($pageView); + $html->assertElementNotContains('.page-content', ''); + $html->assertElementNotContains('.page-content', 'src='); + $html->assertElementNotContains('.page-content', 'javascript:'); + $html->assertElementNotContains('.page-content', 'data:'); + $html->assertElementNotContains('.page-content', 'base64'); + } + } + + public function test_javascript_uri_links_are_removed() + { + $checks = [ + 'withHtml($pageView)->assertElementNotContains('.page-content', 'href=javascript:'); + } + } + + public function test_form_actions_with_javascript_are_removed() + { + $checks = [ + '', + 'Click me', + 'Click me', + '', + '', + ]; + + $this->asEditor(); + $page = $this->entities->page(); + + foreach ($checks as $check) { + $page->html = $check; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $pageView->assertDontSee('id="xss"', false); + $pageView->assertDontSee('action=javascript:', false); + $pageView->assertDontSee('action=JaVaScRiPt:', false); + $pageView->assertDontSee('formaction=javascript:', false); + $pageView->assertDontSee('formaction=JaVaScRiPt:', false); + } + } + + public function test_form_elements_are_removed() + { + $checks = [ + '

    thisisacattofind

    thisdogshouldnotbefound
    ', + '

    thisisacattofind

    ', + '

    thisisacattofind

    ', + '

    thisisacattofind

    ', + '

    thisisacattofind

    thisdogshouldnotbefound
    ', + '

    thisisacattofind

    ', + '

    thisisacattofind

    ', + <<<'TESTCASE' + + + + +

    thisisacattofind

    +
    +

    thisdogshouldnotbefound

    +
    + + + + +
    +
    +TESTCASE + + ]; + + $this->asEditor(); + $page = $this->entities->page(); + + foreach ($checks as $check) { + $page->html = $check; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $pageView->assertSee('thisisacattofind'); + $pageView->assertDontSee('thisdogshouldnotbefound'); + } + } + + public function test_form_attributes_are_removed() + { + $withinSvgSample = <<<'TESTCASE' + + + + +

    thisisacattofind

    +

    thisisacattofind

    + + +
    +
    +TESTCASE; + + $checks = [ + 'formaction' => '

    thisisacattofind

    ', + 'form' => '

    thisisacattofind

    ', + 'formmethod' => '

    thisisacattofind

    ', + 'formtarget' => '

    thisisacattofind

    ', + 'FORMTARGET' => '

    thisisacattofind

    ', + ]; + + $this->asEditor(); + $page = $this->entities->page(); + + foreach ($checks as $attribute => $check) { + $page->html = $check; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $pageView->assertSee('thisisacattofind'); + $this->withHtml($pageView)->assertElementNotExists(".page-content [{$attribute}]"); + } + + $page->html = $withinSvgSample; + $page->save(); + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $html = $this->withHtml($pageView); + foreach ($checks as $attribute => $check) { + $pageView->assertSee('thisisacattofind'); + $html->assertElementNotExists(".page-content [{$attribute}]"); + } + } + + public function test_metadata_redirects_are_removed() + { + $checks = [ + '', + '', + '', + ]; + + $this->asEditor(); + $page = $this->entities->page(); + + foreach ($checks as $check) { + $page->html = $check; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $this->withHtml($pageView)->assertElementNotContains('.page-content', ''); + $this->withHtml($pageView)->assertElementNotContains('.page-content', ''); + $this->withHtml($pageView)->assertElementNotContains('.page-content', 'content='); + $this->withHtml($pageView)->assertElementNotContains('.page-content', 'external_url'); + } + } + + public function test_page_inline_on_attributes_removed_by_default() + { + $this->asEditor(); + $page = $this->entities->page(); + $script = '

    Hello

    '; + $page->html = "escape {$script}"; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $pageView->assertDontSee($script, false); + $pageView->assertSee('

    Hello

    ', false); + } + + public function test_more_complex_inline_on_attributes_escaping_scenarios() + { + $checks = [ + '

    Hello

    ', + '

    Hello

    ', + '
    Lorem ipsum dolor sit amet.

    Hello

    ', + '
    Lorem ipsum dolor sit amet.

    Hello

    ', + '
    Lorem ipsum dolor sit amet.

    Hello

    ', + '
    Lorem ipsum dolor sit amet.

    Hello

    ', + '
    xss link\', + ]; + + $this->asEditor(); + $page = $this->entities->page(); + + foreach ($checks as $check) { + $page->html = $check; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $this->withHtml($pageView)->assertElementNotContains('.page-content', 'onclick'); + } + } + + public function test_page_content_scripts_show_with_filters_disabled() + { + $this->asEditor(); + $page = $this->entities->page(); + config()->set('app.content_filtering', ''); + + $script = 'abc123abc123'; + $page->html = "no escape {$script}"; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertSee($script, false); + $pageView->assertDontSee('abc123abc123'); + } + + public function test_svg_script_usage_is_removed() + { + $checks = [ + '', + '', + '', + '', + '', + 'XSS', + 'XSS', + '', + ]; + + $this->asEditor(); + $page = $this->entities->page(); + + foreach ($checks as $check) { + $page->html = $check; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $html = $this->withHtml($pageView); + $html->assertElementNotContains('.page-content', 'alert'); + $html->assertElementNotContains('.page-content', 'xlink:href'); + $html->assertElementNotContains('.page-content', 'application/xml'); + $html->assertElementNotContains('.page-content', 'javascript'); + } + } + + public function test_page_inline_on_attributes_show_with_filters_disabled() + { + $this->asEditor(); + $page = $this->entities->page(); + config()->set('app.content_filtering', ''); + + $script = '

    Hello

    '; + $page->html = "escape {$script}"; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertSee($script, false); + $pageView->assertDontSee('

    Hello

    ', false); + } +} diff --git a/tests/Entity/PageContentTest.php b/tests/Entity/PageContentTest.php index 77026113012..deae153e192 100644 --- a/tests/Entity/PageContentTest.php +++ b/tests/Entity/PageContentTest.php @@ -101,351 +101,6 @@ public function test_page_includes_to_nonexisting_pages_does_not_error() $pageResp->assertSee('Hello Barry'); } - public function test_page_content_scripts_removed_by_default() - { - $this->asEditor(); - $page = $this->entities->page(); - $script = 'abc123abc123'; - $page->html = "escape {$script}"; - $page->save(); - - $pageView = $this->get($page->getUrl()); - $pageView->assertStatus(200); - $pageView->assertDontSee($script, false); - $pageView->assertSee('abc123abc123'); - } - - public function test_more_complex_content_script_escaping_scenarios() - { - $checks = [ - "

    Some script

    ", - "

    Some script

    ", - "

    Some script

    ", - "

    Some script

    ", - "

    Some script

    ", - "

    Some script

    ", - ]; - - $this->asEditor(); - $page = $this->entities->page(); - - foreach ($checks as $check) { - $page->html = $check; - $page->save(); - - $pageView = $this->get($page->getUrl()); - $pageView->assertStatus(200); - $this->withHtml($pageView)->assertElementNotContains('.page-content', ''); - } - } - - public function test_js_and_base64_src_urls_are_removed() - { - $checks = [ - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - ]; - - $this->asEditor(); - $page = $this->entities->page(); - - foreach ($checks as $check) { - $page->html = $check; - $page->save(); - - $pageView = $this->get($page->getUrl()); - $pageView->assertStatus(200); - $html = $this->withHtml($pageView); - $html->assertElementNotContains('.page-content', ''); - $html->assertElementNotContains('.page-content', 'src='); - $html->assertElementNotContains('.page-content', 'javascript:'); - $html->assertElementNotContains('.page-content', 'data:'); - $html->assertElementNotContains('.page-content', 'base64'); - } - } - - public function test_javascript_uri_links_are_removed() - { - $checks = [ - '
    withHtml($pageView)->assertElementNotContains('.page-content', 'href=javascript:'); - } - } - - public function test_form_actions_with_javascript_are_removed() - { - $checks = [ - '', - 'Click me', - 'Click me', - '', - '', - ]; - - $this->asEditor(); - $page = $this->entities->page(); - - foreach ($checks as $check) { - $page->html = $check; - $page->save(); - - $pageView = $this->get($page->getUrl()); - $pageView->assertStatus(200); - $pageView->assertDontSee('id="xss"', false); - $pageView->assertDontSee('action=javascript:', false); - $pageView->assertDontSee('action=JaVaScRiPt:', false); - $pageView->assertDontSee('formaction=javascript:', false); - $pageView->assertDontSee('formaction=JaVaScRiPt:', false); - } - } - - public function test_form_elements_are_removed() - { - $checks = [ - '

    thisisacattofind

    thisdogshouldnotbefound
    ', - '

    thisisacattofind

    ', - '

    thisisacattofind

    ', - '

    thisisacattofind

    ', - '

    thisisacattofind

    thisdogshouldnotbefound
    ', - '

    thisisacattofind

    ', - '

    thisisacattofind

    ', - <<<'TESTCASE' - - - - -

    thisisacattofind

    -
    -

    thisdogshouldnotbefound

    -
    - - - - -
    -
    -TESTCASE - - ]; - - $this->asEditor(); - $page = $this->entities->page(); - - foreach ($checks as $check) { - $page->html = $check; - $page->save(); - - $pageView = $this->get($page->getUrl()); - $pageView->assertStatus(200); - $pageView->assertSee('thisisacattofind'); - $pageView->assertDontSee('thisdogshouldnotbefound'); - } - } - - public function test_form_attributes_are_removed() - { - $withinSvgSample = <<<'TESTCASE' - - - - -

    thisisacattofind

    -

    thisisacattofind

    - - -
    -
    -TESTCASE; - - $checks = [ - 'formaction' => '

    thisisacattofind

    ', - 'form' => '

    thisisacattofind

    ', - 'formmethod' => '

    thisisacattofind

    ', - 'formtarget' => '

    thisisacattofind

    ', - 'FORMTARGET' => '

    thisisacattofind

    ', - ]; - - $this->asEditor(); - $page = $this->entities->page(); - - foreach ($checks as $attribute => $check) { - $page->html = $check; - $page->save(); - - $pageView = $this->get($page->getUrl()); - $pageView->assertStatus(200); - $pageView->assertSee('thisisacattofind'); - $this->withHtml($pageView)->assertElementNotExists(".page-content [{$attribute}]"); - } - - $page->html = $withinSvgSample; - $page->save(); - $pageView = $this->get($page->getUrl()); - $pageView->assertStatus(200); - $html = $this->withHtml($pageView); - foreach ($checks as $attribute => $check) { - $pageView->assertSee('thisisacattofind'); - $html->assertElementNotExists(".page-content [{$attribute}]"); - } - } - - public function test_metadata_redirects_are_removed() - { - $checks = [ - '', - '', - '', - ]; - - $this->asEditor(); - $page = $this->entities->page(); - - foreach ($checks as $check) { - $page->html = $check; - $page->save(); - - $pageView = $this->get($page->getUrl()); - $pageView->assertStatus(200); - $this->withHtml($pageView)->assertElementNotContains('.page-content', ''); - $this->withHtml($pageView)->assertElementNotContains('.page-content', ''); - $this->withHtml($pageView)->assertElementNotContains('.page-content', 'content='); - $this->withHtml($pageView)->assertElementNotContains('.page-content', 'external_url'); - } - } - - public function test_page_inline_on_attributes_removed_by_default() - { - $this->asEditor(); - $page = $this->entities->page(); - $script = '

    Hello

    '; - $page->html = "escape {$script}"; - $page->save(); - - $pageView = $this->get($page->getUrl()); - $pageView->assertStatus(200); - $pageView->assertDontSee($script, false); - $pageView->assertSee('

    Hello

    ', false); - } - - public function test_more_complex_inline_on_attributes_escaping_scenarios() - { - $checks = [ - '

    Hello

    ', - '

    Hello

    ', - '
    Lorem ipsum dolor sit amet.

    Hello

    ', - '
    Lorem ipsum dolor sit amet.

    Hello

    ', - '
    Lorem ipsum dolor sit amet.

    Hello

    ', - '
    Lorem ipsum dolor sit amet.

    Hello

    ', - '
    xss link\', - ]; - - $this->asEditor(); - $page = $this->entities->page(); - - foreach ($checks as $check) { - $page->html = $check; - $page->save(); - - $pageView = $this->get($page->getUrl()); - $pageView->assertStatus(200); - $this->withHtml($pageView)->assertElementNotContains('.page-content', 'onclick'); - } - } - - public function test_page_content_scripts_show_when_configured() - { - $this->asEditor(); - $page = $this->entities->page(); - config()->set('app.allow_content_scripts', 'true'); - - $script = 'abc123abc123'; - $page->html = "no escape {$script}"; - $page->save(); - - $pageView = $this->get($page->getUrl()); - $pageView->assertSee($script, false); - $pageView->assertDontSee('abc123abc123'); - } - - public function test_svg_script_usage_is_removed() - { - $checks = [ - '', - '', - '', - '', - '', - 'XSS', - 'XSS', - '', - ]; - - $this->asEditor(); - $page = $this->entities->page(); - - foreach ($checks as $check) { - $page->html = $check; - $page->save(); - - $pageView = $this->get($page->getUrl()); - $pageView->assertStatus(200); - $html = $this->withHtml($pageView); - $html->assertElementNotContains('.page-content', 'alert'); - $html->assertElementNotContains('.page-content', 'xlink:href'); - $html->assertElementNotContains('.page-content', 'application/xml'); - $html->assertElementNotContains('.page-content', 'javascript'); - } - } - - public function test_page_inline_on_attributes_show_if_configured() - { - $this->asEditor(); - $page = $this->entities->page(); - config()->set('app.allow_content_scripts', 'true'); - - $script = '

    Hello

    '; - $page->html = "escape {$script}"; - $page->save(); - - $pageView = $this->get($page->getUrl()); - $pageView->assertSee($script, false); - $pageView->assertDontSee('

    Hello

    ', false); - } - public function test_duplicate_ids_does_not_break_page_render() { $this->asEditor(); @@ -649,6 +304,7 @@ public function test_page_markdown_strikethrough_rendering() public function test_page_markdown_single_html_comment_saving() { + config()->set('app.content_filtering', 'jfh'); $this->asEditor(); $page = $this->entities->page(); @@ -656,7 +312,7 @@ public function test_page_markdown_single_html_comment_saving() $this->put($page->getUrl(), [ 'name' => $page->name, 'markdown' => $content, 'html' => '', 'summary' => '', - ]); + ])->assertRedirect(); $page->refresh(); $this->assertStringMatchesFormat($content, $page->html); diff --git a/tests/SecurityHeaderTest.php b/tests/SecurityHeaderTest.php index fe98e32080b..3f4b7d193ce 100644 --- a/tests/SecurityHeaderTest.php +++ b/tests/SecurityHeaderTest.php @@ -93,14 +93,14 @@ public function test_script_csp_nonce_changes_per_request() $this->assertNotEquals($firstHeader, $secondHeader); } - public function test_allow_content_scripts_settings_controls_csp_script_headers() + public function test_content_filtering_config_controls_csp_script_headers() { - config()->set('app.allow_content_scripts', true); + config()->set('app.content_filtering', ''); $resp = $this->get('/'); $scriptHeader = $this->getCspHeader($resp, 'script-src'); $this->assertEmpty($scriptHeader); - config()->set('app.allow_content_scripts', false); + config()->set('app.content_filtering', 'j'); $resp = $this->get('/'); $scriptHeader = $this->getCspHeader($resp, 'script-src'); $this->assertNotEmpty($scriptHeader); From 8a221f64e4e1453966576a06b3bb82393ae735c3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 16 Feb 2026 10:11:48 +0000 Subject: [PATCH 048/204] Content Filtering: Covered new config options and filters with tests --- app/Config/app.php | 8 +-- app/Entities/Tools/PageContent.php | 3 +- phpunit.xml | 1 + tests/Entity/PageContentFilteringTest.php | 75 +++++++++++++++++++++++ tests/Unit/ConfigTest.php | 14 +++++ 5 files changed, 96 insertions(+), 5 deletions(-) diff --git a/app/Config/app.php b/app/Config/app.php index acd27e98c02..7aa94b4f2f9 100644 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -42,17 +42,17 @@ // Even when overridden the WYSIWYG editor may still escape script content. 'allow_content_scripts' => env('ALLOW_CONTENT_SCRIPTS', false), - // Control the behaviour of page content filtering. + // Control the behaviour of content filtering, primarily used for page content. // This setting is a collection of characters which represent different available filters: - // - j - Filter out JavaScript based content - // - h - Filter out unexpected, potentially dangerous, HTML elements + // - j - Filter out JavaScript and unknown binary data based content + // - h - Filter out unexpected, and potentially dangerous, HTML elements // - f - Filter out unexpected form elements // - a - Run content through a more complex allow-list filter // This defaults to using all filters, unless ALLOW_CONTENT_SCRIPTS is set to true in which case no filters are used. // Note: These filters are a best attempt, and may not be 100% effective. They are typically a layer used in addition to other security measures. // TODO - Add to example env // TODO - Remove allow_content_scripts option above - 'content_filtering' => env('CONTENT_FILTERING', env('ALLOW_CONTENT_SCRIPTS', false) === true ? '' : 'jfha'), + 'content_filtering' => env('APP_CONTENT_FILTERING', env('ALLOW_CONTENT_SCRIPTS', false) === true ? '' : 'jhfa'), // Allow server-side fetches to be performed to potentially unknown // and user-provided locations. Primarily used in exports when loading diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index f8a0617395b..4f72e7c490d 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -341,7 +341,8 @@ protected function getContentCacheKey(string $html): string $contentId = $this->page->id; $contentTime = $this->page->updated_at?->timestamp ?? time(); $appVersion = AppVersion::get(); - return "page-content-cache::{$appVersion}::{$contentId}::{$contentTime}::{$contentHash}"; + $filterConfig = config('app.content_filtering') ?? ''; + return "page-content-cache::{$filterConfig}::{$appVersion}::{$contentId}::{$contentTime}::{$contentHash}"; } /** diff --git a/phpunit.xml b/phpunit.xml index 8a7ab9cb7a3..94fc002b704 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -34,6 +34,7 @@ + diff --git a/tests/Entity/PageContentFilteringTest.php b/tests/Entity/PageContentFilteringTest.php index e1295034d68..8103fae1d2b 100644 --- a/tests/Entity/PageContentFilteringTest.php +++ b/tests/Entity/PageContentFilteringTest.php @@ -22,6 +22,8 @@ public function test_page_content_scripts_removed_by_default() public function test_more_complex_content_script_escaping_scenarios() { + config()->set('app.content_filtering', 'j'); + $checks = [ "

    Some script

    ", "

    Some script

    ", @@ -47,6 +49,8 @@ public function test_more_complex_content_script_escaping_scenarios() public function test_js_and_base64_src_urls_are_removed() { + config()->set('app.content_filtering', 'j'); + $checks = [ '', '', @@ -89,6 +93,8 @@ public function test_js_and_base64_src_urls_are_removed() public function test_javascript_uri_links_are_removed() { + config()->set('app.content_filtering', 'j'); + $checks = [ '
    '; + $page->save(); + + $this->asEditor()->get($page->getUrl())->assertSee('dont-see-this', false); + + config()->set('app.content_filtering', 'f'); + $this->get($page->getUrl())->assertDontSee('dont-see-this', false); + } + public function test_form_actions_with_javascript_are_removed() { + config()->set('app.content_filtering', 'j'); + $checks = [ '', 'Click me', @@ -139,6 +160,8 @@ public function test_form_actions_with_javascript_are_removed() public function test_form_elements_are_removed() { + config()->set('app.content_filtering', 'f'); + $checks = [ '

    thisisacattofind

    thisdogshouldnotbefound
    ', '

    thisisacattofind

    ', @@ -182,6 +205,8 @@ public function test_form_elements_are_removed() public function test_form_attributes_are_removed() { + config()->set('app.content_filtering', 'f'); + $withinSvgSample = <<<'TESTCASE' @@ -229,6 +254,8 @@ public function test_form_attributes_are_removed() public function test_metadata_redirects_are_removed() { + config()->set('app.content_filtering', 'h'); + $checks = [ '', '', @@ -253,6 +280,8 @@ public function test_metadata_redirects_are_removed() public function test_page_inline_on_attributes_removed_by_default() { + config()->set('app.content_filtering', 'j'); + $this->asEditor(); $page = $this->entities->page(); $script = '

    Hello

    '; @@ -267,6 +296,8 @@ public function test_page_inline_on_attributes_removed_by_default() public function test_more_complex_inline_on_attributes_escaping_scenarios() { + config()->set('app.content_filtering', 'j'); + $checks = [ '

    Hello

    ', '

    Hello

    ', @@ -308,6 +339,8 @@ public function test_page_content_scripts_show_with_filters_disabled() public function test_svg_script_usage_is_removed() { + config()->set('app.content_filtering', 'j'); + $checks = [ '', '', @@ -350,4 +383,46 @@ public function test_page_inline_on_attributes_show_with_filters_disabled() $pageView->assertSee($script, false); $pageView->assertDontSee('

    Hello

    ', false); } + + public function test_non_content_filtering_is_controlled_by_config() + { + config()->set('app.content_filtering', 'h'); + $page = $this->entities->page(); + $html = <<<'HTML' + +

    inbetweenpsection

    + + +superbeans! + +HTML; + + $page->html = $html; + $page->save(); + + $resp = $this->asEditor()->get($page->getUrl()); + $resp->assertDontSee('superbeans', false); + $resp->assertSee('inbetweenpsection', false); + } + + public function test_non_content_filtering() + { + config()->set('app.content_filtering', 'h'); + } + + public function test_allow_list_filtering_is_controlled_by_config() + { + config()->set('app.content_filtering', ''); + $page = $this->entities->page(); + $page->html = '
    Hello!
    '; + $page->save(); + + $resp = $this->asEditor()->get($page->getUrl()); + $resp->assertSee('style="position: absolute; left: 0;color:#00FFEE;"', false); + + config()->set('app.content_filtering', 'a'); + $resp = $this->get($page->getUrl()); + $resp->assertDontSee('style="position: absolute; left: 0;color:#00FFEE;"', false); + $resp->assertSee('style="color:#00FFEE;"', false); + } } diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php index 7795a861a0c..63dd04fb1d2 100644 --- a/tests/Unit/ConfigTest.php +++ b/tests/Unit/ConfigTest.php @@ -170,6 +170,20 @@ public function test_mysql_host_parsed_as_expected() } } + public function test_content_filtering_defaults_to_enabled() + { + $this->runWithEnv(['APP_CONTENT_FILTERING' => null, 'ALLOW_CONTENT_SCRIPTS' => null], function () { + $this->assertEquals('jhfa', config('app.content_filtering')); + }); + } + + public function test_allow_content_scripts_disables_content_filtering() + { + $this->runWithEnv(['APP_CONTENT_FILTERING' => null, 'ALLOW_CONTENT_SCRIPTS' => 'true'], function () { + $this->assertEquals('', config('app.content_filtering')); + }); + } + /** * Set an environment variable of the given name and value * then check the given config key to see if it matches the given result. From 50e8501027115a70983c6fcecc7c411474221cf1 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 16 Feb 2026 13:02:24 +0000 Subject: [PATCH 049/204] Content Filter: Added extra object filtering Was blocked by CSP anyway, but best to have an extra layer. --- app/Util/HtmlContentFilter.php | 8 +++-- tests/Entity/PageContentFilteringTest.php | 37 +++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/app/Util/HtmlContentFilter.php b/app/Util/HtmlContentFilter.php index 79b1cdc93c4..45144a0e823 100644 --- a/app/Util/HtmlContentFilter.php +++ b/app/Util/HtmlContentFilter.php @@ -61,13 +61,17 @@ protected function filterOutScriptsFromDocument(HtmlDocument $doc): void $badForms = $doc->queryXPath('//*[' . static::xpathContains('@action', 'javascript:') . '] | //*[' . static::xpathContains('@formaction', 'javascript:') . ']'); static::removeNodes($badForms); - // Remove data or JavaScript iFrames + // Remove data or JavaScript iFrames & embeds $badIframes = $doc->queryXPath('//*[' . static::xpathContains('@src', 'data:') . '] | //*[' . static::xpathContains('@src', 'javascript:') . '] | //*[@srcdoc]'); static::removeNodes($badIframes); + // Remove data or JavaScript objects + $badObjects = $doc->queryXPath('//*[' . static::xpathContains('@data', 'data:') . '] | //*[' . static::xpathContains('@data', 'javascript:') . ']'); + static::removeNodes($badObjects); + // Remove attributes, within svg children, hiding JavaScript or data uris. // A bunch of svg element and attribute combinations expose xss possibilities. - // For example, SVG animate tag can exploit javascript in values. + // For example, SVG animate tag can exploit JavaScript in values. $badValuesAttrs = $doc->queryXPath('//svg//@*[' . static::xpathContains('.', 'data:') . '] | //svg//@*[' . static::xpathContains('.', 'javascript:') . ']'); static::removeAttributes($badValuesAttrs); diff --git a/tests/Entity/PageContentFilteringTest.php b/tests/Entity/PageContentFilteringTest.php index 8103fae1d2b..98297a093b8 100644 --- a/tests/Entity/PageContentFilteringTest.php +++ b/tests/Entity/PageContentFilteringTest.php @@ -69,6 +69,20 @@ public function test_js_and_base64_src_urls_are_removed() '', '', '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', ]; $this->asEditor(); @@ -81,6 +95,8 @@ public function test_js_and_base64_src_urls_are_removed() $pageView = $this->get($page->getUrl()); $pageView->assertStatus(200); $html = $this->withHtml($pageView); + $html->assertElementNotContains('.page-content', 'assertElementNotContains('.page-content', 'data='); $html->assertElementNotContains('.page-content', ''); @@ -425,4 +441,25 @@ public function test_allow_list_filtering_is_controlled_by_config() $resp->assertDontSee('style="position: absolute; left: 0;color:#00FFEE;"', false); $resp->assertSee('style="color:#00FFEE;"', false); } + + public function test_allow_list_style_filtering() + { + $testCasesExpectedByInput = [ + '
    Hello!
    ' => '
    Hello!
    ', + '
    Hello!
    ' => '
    Hello!
    ', + '
    Hello!
    ' => '
    Hello!
    ', + ]; + + config()->set('app.content_filtering', 'a'); + $page = $this->entities->page(); + $this->asEditor(); + + foreach ($testCasesExpectedByInput as $input => $expected) { + $page->html = $input; + $page->save(); + $resp = $this->get($page->getUrl()); + + $resp->assertSee($expected, false); + } + } } From 3fa1174e7a879fe8d128e89709889628c496838d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 16 Feb 2026 13:46:45 +0000 Subject: [PATCH 050/204] Content filtering: Updated config and readme attribution --- .env.example.complete | 17 ++++++++++++++++- app/Config/app.php | 13 +++---------- readme.md | 5 +++-- tests/Unit/ConfigTest.php | 7 +++++++ 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/.env.example.complete b/.env.example.complete index 18e7bd00d9c..ebebaf9e3e8 100644 --- a/.env.example.complete +++ b/.env.example.complete @@ -351,10 +351,25 @@ EXPORT_PDF_COMMAND_TIMEOUT=15 # Only used if 'ALLOW_UNTRUSTED_SERVER_FETCHING=true' which disables security protections. WKHTMLTOPDF=false -# Allow

    '; + $page->save(); $this->getJson('/ajax/page/' . $page->id)->assertJson([ - 'html' => $page->html, + 'html' => '

    test content

    ', ]); } From a2017ffa559d96669f207863ca202d2e50363622 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 17 Feb 2026 18:22:13 +0000 Subject: [PATCH 055/204] Caching: Altered purifier cache folder to be server-created Moved from a static folder to a dynamically created folder in the framework/cache directory, to increase the chance that it's created with server-writable permissions. This is due to an issue where users had permission issues, since adding a new folder means it's created by the git user and often non-web-writable. --- app/Util/ConfiguredHtmlPurifier.php | 18 +++++++++++++++--- storage/framework/purifier/.gitignore | 2 -- 2 files changed, 15 insertions(+), 5 deletions(-) delete mode 100644 storage/framework/purifier/.gitignore diff --git a/app/Util/ConfiguredHtmlPurifier.php b/app/Util/ConfiguredHtmlPurifier.php index 014b2a3bf2b..87580da8bf1 100644 --- a/app/Util/ConfiguredHtmlPurifier.php +++ b/app/Util/ConfiguredHtmlPurifier.php @@ -22,8 +22,13 @@ class ConfiguredHtmlPurifier public function __construct() { + // This is done by the web-server at run-time, with the existing + // storage/framework/cache folder to ensure we're using a server-writable folder. + $cachePath = storage_path('framework/cache/purifier'); + $this->createCacheFolderIfNeeded($cachePath); + $config = HTMLPurifier_HTML5Config::createDefault(); - $this->setConfig($config); + $this->setConfig($config, $cachePath); $this->resetCacheIfNeeded($config); $htmlDef = $config->getDefinition('HTML', true, true); @@ -34,6 +39,13 @@ public function __construct() $this->purifier = new HTMLPurifier($config); } + protected function createCacheFolderIfNeeded(string $cachePath): void + { + if (!file_exists($cachePath)) { + mkdir($cachePath, 0777, true); + } + } + protected function resetCacheIfNeeded(HTMLPurifier_Config $config): void { if (self::$cachedChecked) { @@ -53,9 +65,9 @@ protected function resetCacheIfNeeded(HTMLPurifier_Config $config): void self::$cachedChecked = true; } - protected function setConfig(HTMLPurifier_Config $config): void + protected function setConfig(HTMLPurifier_Config $config, string $cachePath): void { - $config->set('Cache.SerializerPath', storage_path('framework/purifier')); + $config->set('Cache.SerializerPath', $cachePath); $config->set('Core.AllowHostnameUnderscore', true); $config->set('CSS.AllowTricky', true); $config->set('HTML.SafeIframe', true); diff --git a/storage/framework/purifier/.gitignore b/storage/framework/purifier/.gitignore deleted file mode 100644 index c96a04f008e..00000000000 --- a/storage/framework/purifier/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file From e1de1f0583352a71161a1eea5ca3b54c847dc95f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 17 Feb 2026 18:34:14 +0000 Subject: [PATCH 056/204] git: Added old purifier location to gitignore --- storage/framework/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore index 05c4471f2b5..8d89041780c 100755 --- a/storage/framework/.gitignore +++ b/storage/framework/.gitignore @@ -7,3 +7,4 @@ routes.php routes.scanned.php schedule-* services.json +purifier/ From 9d15c79feec450be21b44f6828d62f6feef01b6a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 18 Feb 2026 19:24:06 +0000 Subject: [PATCH 057/204] Deps: Updated PHP package versions --- composer.lock | 54 +++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/composer.lock b/composer.lock index 08a932a38a1..c72b6dd1bdb 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.369.35", + "version": "3.369.36", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "0f3e296342fe965271b5dd0bded4a18bdab8aba5" + "reference": "2a69e7df5e03be9e08f9f73fb6a8cc9dd63b59c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0f3e296342fe965271b5dd0bded4a18bdab8aba5", - "reference": "0f3e296342fe965271b5dd0bded4a18bdab8aba5", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2a69e7df5e03be9e08f9f73fb6a8cc9dd63b59c0", + "reference": "2a69e7df5e03be9e08f9f73fb6a8cc9dd63b59c0", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.369.35" + "source": "https://github.com/aws/aws-sdk-php/tree/3.369.36" }, - "time": "2026-02-16T19:15:41+00:00" + "time": "2026-02-17T19:45:01+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1800,16 +1800,16 @@ }, { "name": "laravel/framework", - "version": "v12.51.0", + "version": "v12.52.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "ce4de3feb211e47c4f959d309ccf8a2733b1bc16" + "reference": "d5511fa74f4608dbb99864198b1954042aa8d5a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/ce4de3feb211e47c4f959d309ccf8a2733b1bc16", - "reference": "ce4de3feb211e47c4f959d309ccf8a2733b1bc16", + "url": "https://api.github.com/repos/laravel/framework/zipball/d5511fa74f4608dbb99864198b1954042aa8d5a7", + "reference": "d5511fa74f4608dbb99864198b1954042aa8d5a7", "shasum": "" }, "require": { @@ -2018,7 +2018,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-10T18:20:19+00:00" + "time": "2026-02-17T17:07:04+00:00" }, { "name": "laravel/prompts", @@ -8946,36 +8946,36 @@ }, { "name": "nunomaduro/collision", - "version": "v8.9.0", + "version": "v8.9.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "f52cab234f37641bd759c0ad56de17f632851419" + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f52cab234f37641bd759c0ad56de17f632851419", - "reference": "f52cab234f37641bd759c0ad56de17f632851419", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", "shasum": "" }, "require": { "filp/whoops": "^2.18.4", - "nunomaduro/termwind": "^2.3.3", + "nunomaduro/termwind": "^2.4.0", "php": "^8.2.0", "symfony/console": "^7.4.4 || ^8.0.4" }, "conflict": { "laravel/framework": "<11.48.0 || >=14.0.0", - "phpunit/phpunit": "<11.5.50 || >=13.0.0" + "phpunit/phpunit": "<11.5.50 || >=14.0.0" }, "require-dev": { "brianium/paratest": "^7.8.5", "larastan/larastan": "^3.9.2", - "laravel/framework": "^11.48.0 || ^12.51.0", + "laravel/framework": "^11.48.0 || ^12.52.0", "laravel/pint": "^1.27.1", "orchestra/testbench-core": "^9.12.0 || ^10.9.0", - "pestphp/pest": "^3.8.5 || ^4.3.2", - "sebastian/environment": "^7.2.1 || ^8.0.3" + "pestphp/pest": "^3.8.5 || ^4.4.1 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.0.3 || ^9.0.0" }, "type": "library", "extra": { @@ -9038,7 +9038,7 @@ "type": "patreon" } ], - "time": "2026-02-16T23:05:52+00:00" + "time": "2026-02-17T17:33:08+00:00" }, { "name": "phar-io/manifest", @@ -9560,16 +9560,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.53", + "version": "11.5.55", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a997a653a82845f1240d73ee73a8a4e97e4b0607" + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a997a653a82845f1240d73ee73a8a4e97e4b0607", - "reference": "a997a653a82845f1240d73ee73a8a4e97e4b0607", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", "shasum": "" }, "require": { @@ -9642,7 +9642,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.53" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" }, "funding": [ { @@ -9666,7 +9666,7 @@ "type": "tidelift" } ], - "time": "2026-02-10T12:28:25+00:00" + "time": "2026-02-18T12:37:06+00:00" }, { "name": "sebastian/cli-parser", From a8d96fd3892c5f98136e93c7d50fa689f0869b3d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 18 Feb 2026 19:33:35 +0000 Subject: [PATCH 058/204] Content filter: Allowed custom diagram attribute in allow-list For #6026 --- app/Util/ConfiguredHtmlPurifier.php | 7 +++++++ tests/Entity/PageContentFilteringTest.php | 1 + 2 files changed, 8 insertions(+) diff --git a/app/Util/ConfiguredHtmlPurifier.php b/app/Util/ConfiguredHtmlPurifier.php index 87580da8bf1..ab23333882a 100644 --- a/app/Util/ConfiguredHtmlPurifier.php +++ b/app/Util/ConfiguredHtmlPurifier.php @@ -134,6 +134,13 @@ public function configureDefinition(HTMLPurifier_HTMLDefinition $definition): vo 'value' => 'Text', ] ); + + // Allow the drawio-diagram attribute on div elements + $definition->addAttribute( + 'div', + 'drawio-diagram', + 'Number', + ); } public function purify(string $html): string diff --git a/tests/Entity/PageContentFilteringTest.php b/tests/Entity/PageContentFilteringTest.php index c048c09dc5f..4f77e063369 100644 --- a/tests/Entity/PageContentFilteringTest.php +++ b/tests/Entity/PageContentFilteringTest.php @@ -463,6 +463,7 @@ public function test_allow_list_style_filtering() '
    Hello!
    ' => '
    Hello!
    ', '
    Hello!
    ' => '
    Hello!
    ', '
    Hello!
    ' => '
    Hello!
    ', + '
    Hello!
    ' => '
    Hello!
    ', ]; config()->set('app.content_filtering', 'a'); From 80204518a25bb98beb0790f06496f19ec66d61ca Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 19 Feb 2026 23:25:00 +0000 Subject: [PATCH 059/204] Page Content: Better handling for empty content filtering For #6028 --- app/Util/HtmlDocument.php | 8 +++++++- tests/Entity/PageEditorTest.php | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/Util/HtmlDocument.php b/app/Util/HtmlDocument.php index 7517955b9f6..c1ec2cf415b 100644 --- a/app/Util/HtmlDocument.php +++ b/app/Util/HtmlDocument.php @@ -103,7 +103,13 @@ public function getElementById(string $elementId): ?DOMElement */ public function getBody(): DOMNode { - return $this->document->getElementsByTagName('body')[0]; + $bodies = $this->document->getElementsByTagName('body'); + + if ($bodies->length === 0) { + return new DOMElement('body', ''); + } + + return $bodies[0]; } /** diff --git a/tests/Entity/PageEditorTest.php b/tests/Entity/PageEditorTest.php index 4cd3c1671c5..67283f70420 100644 --- a/tests/Entity/PageEditorTest.php +++ b/tests/Entity/PageEditorTest.php @@ -282,4 +282,23 @@ public function test_editor_html_content_is_filtered_if_loaded_by_a_different_us $resp->assertOk(); $resp->assertDontSee('hellotherethisisaturtlemonster', false); } + + public function test_editor_html_filtered_does_not_cause_error_if_empty() + { + $emptyExamples = ['', '

    ', '

     

    ', ' ', "\n"]; + $editor = $this->users->editor(); + $page = $this->entities->page(); + $page->updated_by = $editor->id; + + foreach ($emptyExamples as $emptyExample) { + $page->html = $emptyExample; + $page->save(); + + $resp = $this->asAdmin()->get($page->getUrl('edit')); + $resp->assertOk(); + + $resp = $this->asAdmin()->get("/ajax/page/{$page->id}"); + $resp->assertOk(); + } + } } From 8e99fc678352a9757348cfa1a74982b3c7f6cfa6 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 20 Feb 2026 11:23:26 +0000 Subject: [PATCH 060/204] Books: On delete, redirect to shelf if in context For #6029 Added tests to cover --- app/Entities/Controllers/BookController.php | 5 +++++ tests/Entity/BookTest.php | 14 ++++++++++++++ tests/Helpers/EntityProvider.php | 8 ++++++++ 3 files changed, 27 insertions(+) diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php index c94057fa99b..fca530f8adf 100644 --- a/app/Entities/Controllers/BookController.php +++ b/app/Entities/Controllers/BookController.php @@ -224,9 +224,14 @@ public function destroy(string $bookSlug) { $book = $this->queries->findVisibleBySlugOrFail($bookSlug); $this->checkOwnablePermission(Permission::BookDelete, $book); + $contextShelf = $this->shelfContext->getContextualShelfForBook($book); $this->bookRepo->destroy($book); + if ($contextShelf) { + return redirect($contextShelf->getUrl()); + } + return redirect('/books'); } diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php index a7142f03736..5f0aabc38ca 100644 --- a/tests/Entity/BookTest.php +++ b/tests/Entity/BookTest.php @@ -154,6 +154,20 @@ public function test_delete() $this->assertNotificationContains($redirectReq, 'Book Successfully Deleted'); } + public function test_delete_with_shelf_context_returns_to_shelf_view_after_delete() + { + $shelf = $this->entities->shelfHasBooks(); + /** @var Book $book */ + $book = $shelf->books()->first(); + + $this->asEditor()->get($shelf->getUrl()); + $this->get($book->getUrl()); + $this->get($book->getUrl('/delete')); + $resp = $this->delete($book->getUrl()); + + $resp->assertRedirect($shelf->getUrl()); + } + public function test_cancel_on_create_page_leads_back_to_books_listing() { $resp = $this->asEditor()->get('/create-book'); diff --git a/tests/Helpers/EntityProvider.php b/tests/Helpers/EntityProvider.php index 5163cef14a0..d4cdce51285 100644 --- a/tests/Helpers/EntityProvider.php +++ b/tests/Helpers/EntityProvider.php @@ -110,6 +110,14 @@ public function shelf(callable|null $queryFilter = null): Bookshelf return $shelf; } + /** + * Get a shelf that has books assigned. + */ + public function shelfHasBooks(): Bookshelf + { + return $this->shelf(fn(Builder $query) => $query->whereHas('books')); + } + /** * Get all entity types from the system. * @return array{page: Page, chapter: Chapter, book: Book, bookshelf: Bookshelf} From 229a99ba24d7676f5608bcb662bbe047d4a3bf42 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 20 Feb 2026 14:22:54 +0000 Subject: [PATCH 061/204] Descriptions: Improved empty field handling, reduces whitespace For #5724 --- app/Entities/Tools/EntityHtmlDescription.php | 5 +++++ .../form/description-html-input.blade.php | 2 +- tests/Entity/BookTest.php | 21 +++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/Entities/Tools/EntityHtmlDescription.php b/app/Entities/Tools/EntityHtmlDescription.php index b14deb257a7..795aaf4aac1 100644 --- a/app/Entities/Tools/EntityHtmlDescription.php +++ b/app/Entities/Tools/EntityHtmlDescription.php @@ -50,6 +50,11 @@ public function getHtml(bool $raw = false): string return $html; } + $isEmpty = empty(trim(strip_tags($html))); + if ($isEmpty) { + return '

    '; + } + return HtmlContentFilter::removeActiveContentFromHtmlString($html); } diff --git a/resources/views/form/description-html-input.blade.php b/resources/views/form/description-html-input.blade.php index 983d2fb8371..4b0a74df1b3 100644 --- a/resources/views/form/description-html-input.blade.php +++ b/resources/views/form/description-html-input.blade.php @@ -1,7 +1,7 @@ + @if($errors->has('description_html')) class="text-neg" @endif>@if(isset($model) || old('description_html')){{ old('description_html') ?? $model->descriptionInfo()->getHtml() }}@else{{ '

    ' }}@endif @if($errors->has('description_html'))
    {{ $errors->first('description_html') }}
    @endif \ No newline at end of file diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php index 5f0aabc38ca..6082c59de61 100644 --- a/tests/Entity/BookTest.php +++ b/tests/Entity/BookTest.php @@ -278,4 +278,25 @@ public function test_show_view_displays_description_if_no_description_html_set() $resp = $this->asEditor()->get($book->getUrl()); $resp->assertSee("

    My great
    \ndescription
    \n
    \nwith newlines

    ", false); } + + public function test_description_with_only_br_tags_results_in_empty_p_tag_used_on_show() + { + $descriptions = [ + '


    ', + '





    ', + '









    ', + ]; + $book = $this->entities->book(); + $this->asEditor(); + + foreach ($descriptions as $descriptionTestCase) { + $book->description_html = $descriptionTestCase; + $book->save(); + + $resp = $this->get($book->getUrl()); + $html = $this->withHtml($resp); + $descriptionHtml = $html->getInnerHtml('.book-content > div.text-muted:first-child'); + $this->assertEquals('

    ', $descriptionHtml); + } + } } From 23f3f35f6b23a5b491b181c1d1628304a17ef6da Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 21 Feb 2026 13:56:50 +0000 Subject: [PATCH 062/204] Readme: Updated sponsors --- readme.md | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/readme.md b/readme.md index d134a24799a..e655c538f3c 100644 --- a/readme.md +++ b/readme.md @@ -48,17 +48,13 @@ Big thanks to these companies for supporting the project. #### Gold Sponsor - -
    - Federated.computer -
    Diagrams.net
    - onyx.app + onyx.app
    @@ -81,26 +77,23 @@ Big thanks to these companies for supporting the project. - - Schroeck IT Consulting - Practinet - - Route4Me - Route Optimizer and Route Planner Software + + phamos - - SiteSpeakAI - + + + Admin Intelligence From 6808292c90ce9cced8562651b12635f5ac6a3f3b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 21 Feb 2026 15:56:12 +0000 Subject: [PATCH 063/204] Editors: Made drawings appear clickiable via cursor During review of #5864 --- resources/sass/_editor.scss | 3 +++ resources/sass/_forms.scss | 3 +++ resources/sass/_tinymce.scss | 3 +-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss index cd4bb5d2ef0..0580d7377c9 100644 --- a/resources/sass/_editor.scss +++ b/resources/sass/_editor.scss @@ -451,6 +451,9 @@ body.editor-is-fullscreen { outline: 1px dashed var(--editor-color-primary); outline-offset: 1px; } +.editor-content-area [drawio-diagram] { + cursor: pointer; +} .editor-table-marker { position: fixed; diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss index 13a4232fc7e..f1a2feabf5c 100644 --- a/resources/sass/_forms.scss +++ b/resources/sass/_forms.scss @@ -142,6 +142,9 @@ padding-inline-end: 12px; max-width: 864px; } + [drawio-diagram] { + cursor: pointer; + } [drawio-diagram]:hover { outline: 2px solid var(--color-primary); } diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss index 561bb23cae0..d662550c613 100644 --- a/resources/sass/_tinymce.scss +++ b/resources/sass/_tinymce.scss @@ -205,9 +205,8 @@ body.page-content.mce-content-body { } /** - * Set correct cursor for drawio + * Ensure cursor indicates that drawings are clickable */ - .page-content.mce-content-body [drawio-diagram] { cursor: pointer; } From 7aef0a48b309d3e74193a1e5a7a236147f307f32 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 23 Feb 2026 08:07:41 +0000 Subject: [PATCH 064/204] Content: Updated filters to allow some required attributes - Allows target attribute on links. - Allows custom mention attribute on links. Adds test case to cover these. For #6034 --- app/Util/ConfiguredHtmlPurifier.php | 8 ++++++++ tests/Entity/PageContentFilteringTest.php | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/app/Util/ConfiguredHtmlPurifier.php b/app/Util/ConfiguredHtmlPurifier.php index ab23333882a..1f2528e7155 100644 --- a/app/Util/ConfiguredHtmlPurifier.php +++ b/app/Util/ConfiguredHtmlPurifier.php @@ -71,6 +71,8 @@ protected function setConfig(HTMLPurifier_Config $config, string $cachePath): vo $config->set('Core.AllowHostnameUnderscore', true); $config->set('CSS.AllowTricky', true); $config->set('HTML.SafeIframe', true); + $config->set('HTML.TargetNoopener', false); + $config->set('HTML.TargetNoreferrer', false); $config->set('Attr.EnableID', true); $config->set('Attr.ID.HTML5', true); $config->set('Output.FixInnerHTML', false); @@ -141,6 +143,12 @@ public function configureDefinition(HTMLPurifier_HTMLDefinition $definition): vo 'drawio-diagram', 'Number', ); + + // Allow target="_blank" on links + $definition->addAttribute('a', 'target', 'Enum#_blank'); + + // Allow mention-ids on links + $definition->addAttribute('a', 'data-mention-user-id', 'Number'); } public function purify(string $html): string diff --git a/tests/Entity/PageContentFilteringTest.php b/tests/Entity/PageContentFilteringTest.php index 4f77e063369..449189a898c 100644 --- a/tests/Entity/PageContentFilteringTest.php +++ b/tests/Entity/PageContentFilteringTest.php @@ -478,4 +478,25 @@ public function test_allow_list_style_filtering() $resp->assertSee($expected, false); } } + + public function test_allow_list_does_not_filter_cases() + { + $testCasesExpectedByInput = [ + '

    New tab linkydoodle

    ', + '

    @mentionusertext

    ', + '
    Hello

    Mydetailshere

    ', + ]; + + config()->set('app.content_filtering', 'a'); + $page = $this->entities->page(); + $this->asEditor(); + + foreach ($testCasesExpectedByInput as $input) { + $page->html = $input; + $page->save(); + $resp = $this->get($page->getUrl()); + + $resp->assertSee($input, false); + } + } } From 9a12e3a8b78f0334fc7e376c9d805037edbd3aa3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 24 Feb 2026 10:25:17 +0000 Subject: [PATCH 065/204] Book API: Added shelves list to show endpoint For #6006 Added test to cover. --- .../Controllers/BookApiController.php | 15 +++++++-- app/Entities/Models/Bookshelf.php | 2 +- tests/Api/BooksApiTest.php | 31 +++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/app/Entities/Controllers/BookApiController.php b/app/Entities/Controllers/BookApiController.php index 325f0583c67..c47ece22569 100644 --- a/app/Entities/Controllers/BookApiController.php +++ b/app/Entities/Controllers/BookApiController.php @@ -7,11 +7,14 @@ use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Queries\BookQueries; +use BookStack\Entities\Queries\BookshelfQueries; use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Tools\BookContents; use BookStack\Http\ApiController; use BookStack\Permissions\Permission; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; @@ -21,6 +24,7 @@ public function __construct( protected BookRepo $bookRepo, protected BookQueries $queries, protected PageQueries $pageQueries, + protected BookshelfQueries $shelfQueries, ) { } @@ -60,13 +64,20 @@ public function create(Request $request) * View the details of a single book. * The response data will contain a 'content' property listing the chapter and pages directly within, in * the same structure as you'd see within the BookStack interface when viewing a book. Top-level - * contents will have a 'type' property to distinguish between pages & chapters. + * contents will have a 'type' property to distinguish between pages and chapters. */ public function read(string $id) { $book = $this->queries->findVisibleByIdOrFail(intval($id)); $book = $this->forJsonDisplay($book); - $book->load(['createdBy', 'updatedBy', 'ownedBy']); + $book->load([ + 'createdBy', + 'updatedBy', + 'ownedBy', + 'shelves' => function (BelongsToMany $query) { + $query->select(['id', 'name', 'slug'])->scopes('visible'); + } + ]); $contents = (new BookContents($book))->getTree(true, false)->all(); $contentsApiData = (new ApiEntityListFormatter($contents)) diff --git a/app/Entities/Models/Bookshelf.php b/app/Entities/Models/Bookshelf.php index 42dcc8f8f2c..320346512e1 100644 --- a/app/Entities/Models/Bookshelf.php +++ b/app/Entities/Models/Bookshelf.php @@ -19,7 +19,7 @@ class Bookshelf extends Entity implements HasDescriptionInterface, HasCoverInter public float $searchFactor = 1.2; - protected $hidden = ['image_id', 'deleted_at', 'description_html', 'priority', 'default_template_id', 'sort_rule_id', 'entity_id', 'entity_type', 'chapter_id', 'book_id']; + protected $hidden = ['pivot', 'image_id', 'deleted_at', 'description_html', 'priority', 'default_template_id', 'sort_rule_id', 'entity_id', 'entity_type', 'chapter_id', 'book_id']; protected $fillable = ['name']; /** diff --git a/tests/Api/BooksApiTest.php b/tests/Api/BooksApiTest.php index 86e10f58acb..74f558f381b 100644 --- a/tests/Api/BooksApiTest.php +++ b/tests/Api/BooksApiTest.php @@ -188,6 +188,37 @@ public function test_read_endpoint_contents_nested_pages_has_permissions_applied $resp->assertJsonMissing(['name' => $customName]); } + public function test_read_endpoint_lists_visible_shelves_the_book_is_assigned_to() + { + $this->actingAsApiEditor(); + $shelf = $this->entities->shelf(); + $otherShelf = $this->entities->shelf(); + $book = $this->entities->book(); + $book->shelves()->detach(); + + $book->shelves()->attach($shelf); + $book->shelves()->attach($otherShelf); + + $this->assertEquals(2, $book->shelves()->count()); + + $this->permissions->disableEntityInheritedPermissions($otherShelf); + + $resp = $this->getJson("{$this->baseEndpoint}/{$book->id}"); + $resp->assertOk(); + $resp->assertJsonCount(1, 'shelves'); + $resp->assertJson([ + 'shelves' => [ + [ + 'id' => $shelf->id, + 'name' => $shelf->name, + 'slug' => $shelf->slug, + ] + ] + ]); + $resp->assertJsonMissingPath('shelves.0.description'); + $resp->assertJsonMissingPath('shelves.0.pivot'); + } + public function test_update_endpoint() { $this->actingAsApiEditor(); From dd42b9b43f14ed7a8ee06b72fe8be32394c81564 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 27 Feb 2026 08:54:12 +0000 Subject: [PATCH 066/204] Text: Updated per-page display limits description To be more sensible & direct as per MtheBird's suggestion. Closes #6005 --- lang/en/settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/en/settings.php b/lang/en/settings.php index c68605fe1f8..c4d1eb136eb 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Maintenance', From 10c46534e0d582480316d851356710e18b2143c9 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 27 Feb 2026 09:34:33 +0000 Subject: [PATCH 067/204] Logical Theme: Added OIDC_AUTH_PRE_REDIRECT event Allows customization of the auth URL before the user is redirected to that URL. Related to #6014 --- app/Access/Oidc/OidcService.php | 5 +++++ app/Theming/ThemeEvents.php | 11 +++++++++++ tests/Auth/OidcTest.php | 28 ++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/app/Access/Oidc/OidcService.php b/app/Access/Oidc/OidcService.php index d6f6ef156e4..a84bd320513 100644 --- a/app/Access/Oidc/OidcService.php +++ b/app/Access/Oidc/OidcService.php @@ -49,6 +49,11 @@ public function login(): array $url = $provider->getAuthorizationUrl(); session()->put('oidc_pkce_code', $provider->getPkceCode() ?? ''); + $returnUrl = Theme::dispatch(ThemeEvents::OIDC_AUTH_PRE_REDIRECT, $url); + if (is_string($returnUrl)) { + $url = $returnUrl; + } + return [ 'url' => $url, 'state' => $provider->getState(), diff --git a/app/Theming/ThemeEvents.php b/app/Theming/ThemeEvents.php index c6266b32b9c..71778ec4484 100644 --- a/app/Theming/ThemeEvents.php +++ b/app/Theming/ThemeEvents.php @@ -87,6 +87,17 @@ class ThemeEvents */ const COMMONMARK_ENVIRONMENT_CONFIGURE = 'commonmark_environment_configure'; + /** + * OIDC auth pre-redirect event. + * Runs just before BookStack redirects the user to the identity provider for authentication. + * Provides the redirect URL that will be used. + * If the listener returns a string value, that will be used as the redirect URL instead. + * + * @param string $redirectUrl + * @return string|null + */ + const OIDC_AUTH_PRE_REDIRECT = 'oidc_auth_pre_redirect'; + /** * OIDC ID token pre-validate event. * Runs just before BookStack validates the user ID token data upon login. diff --git a/tests/Auth/OidcTest.php b/tests/Auth/OidcTest.php index 710e5375785..8508568f1f4 100644 --- a/tests/Auth/OidcTest.php +++ b/tests/Auth/OidcTest.php @@ -822,6 +822,34 @@ public function test_oidc_id_token_pre_validate_theme_event_with_return() ]); } + public function test_oidc_auth_pre_redirect_theme_event_with_return() + { + $args = []; + $callback = function (...$eventArgs) use (&$args) { + $args = $eventArgs; + return 'https://cats.example.com?beans=true'; + }; + Theme::listen(ThemeEvents::OIDC_AUTH_PRE_REDIRECT, $callback); + + $resp = $this->post('/oidc/login'); + $resp->assertRedirect('https://cats.example.com?beans=true'); + + $this->assertCount(1, $args); + $this->assertStringStartsWith('https://oidc.local/auth', $args[0]); + } + + public function test_oidc_auth_pre_redirect_theme_event_with_no_return() + { + $callback = function ($redirectUrl) { + $redirectUrl = 'cat'; + }; + Theme::listen(ThemeEvents::OIDC_AUTH_PRE_REDIRECT, $callback); + + $resp = $this->post('/oidc/login'); + $redirect = $resp->headers->get('Location'); + $this->assertStringStartsWith('https://oidc.local/auth?', $redirect); + } + public function test_pkce_used_on_authorize_and_access() { // Start auth From 25ed242f61293731210e7962ac63561796396654 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 27 Feb 2026 10:09:41 +0000 Subject: [PATCH 068/204] Deps: Updated PHP package versions --- composer.lock | 330 ++++++++++++++++++++++++++------------------------ 1 file changed, 169 insertions(+), 161 deletions(-) diff --git a/composer.lock b/composer.lock index c72b6dd1bdb..18d0da62191 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.369.36", + "version": "3.371.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "2a69e7df5e03be9e08f9f73fb6a8cc9dd63b59c0" + "reference": "32090a8ac3ec8859cb83bdde800b8f0ecf92d8ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2a69e7df5e03be9e08f9f73fb6a8cc9dd63b59c0", - "reference": "2a69e7df5e03be9e08f9f73fb6a8cc9dd63b59c0", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/32090a8ac3ec8859cb83bdde800b8f0ecf92d8ec", + "reference": "32090a8ac3ec8859cb83bdde800b8f0ecf92d8ec", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.369.36" + "source": "https://github.com/aws/aws-sdk-php/tree/3.371.2" }, - "time": "2026-02-17T19:45:01+00:00" + "time": "2026-02-26T19:06:10+00:00" }, { "name": "bacon/bacon-qr-code", @@ -982,16 +982,16 @@ }, { "name": "firebase/php-jwt", - "version": "v7.0.2", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65" + "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/5645b43af647b6947daac1d0f659dd1fbe8d3b65", - "reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/28aa0694bcfdfa5e2959c394d5a1ee7a5083629e", + "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e", "shasum": "" }, "require": { @@ -1039,9 +1039,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v7.0.2" + "source": "https://github.com/firebase/php-jwt/tree/v7.0.3" }, - "time": "2025-12-16T22:17:28+00:00" + "time": "2026-02-25T22:16:40+00:00" }, { "name": "fruitcake/php-cors", @@ -1657,16 +1657,16 @@ }, { "name": "intervention/image", - "version": "3.11.6", + "version": "3.11.7", "source": { "type": "git", "url": "https://github.com/Intervention/image.git", - "reference": "5f6d27d9fd56312c47f347929e7ac15345c605a1" + "reference": "2159bcccff18f09d2a392679b81a82c5a003f9bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/5f6d27d9fd56312c47f347929e7ac15345c605a1", - "reference": "5f6d27d9fd56312c47f347929e7ac15345c605a1", + "url": "https://api.github.com/repos/Intervention/image/zipball/2159bcccff18f09d2a392679b81a82c5a003f9bb", + "reference": "2159bcccff18f09d2a392679b81a82c5a003f9bb", "shasum": "" }, "require": { @@ -1713,7 +1713,7 @@ ], "support": { "issues": "https://github.com/Intervention/image/issues", - "source": "https://github.com/Intervention/image/tree/3.11.6" + "source": "https://github.com/Intervention/image/tree/3.11.7" }, "funding": [ { @@ -1729,7 +1729,7 @@ "type": "ko_fi" } ], - "time": "2025-12-17T13:38:29+00:00" + "time": "2026-02-19T13:11:17+00:00" }, { "name": "knplabs/knp-snappy", @@ -1800,16 +1800,16 @@ }, { "name": "laravel/framework", - "version": "v12.52.0", + "version": "v12.53.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "d5511fa74f4608dbb99864198b1954042aa8d5a7" + "reference": "f57f035c0d34503d9ff30be76159bb35a003cd1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/d5511fa74f4608dbb99864198b1954042aa8d5a7", - "reference": "d5511fa74f4608dbb99864198b1954042aa8d5a7", + "url": "https://api.github.com/repos/laravel/framework/zipball/f57f035c0d34503d9ff30be76159bb35a003cd1f", + "reference": "f57f035c0d34503d9ff30be76159bb35a003cd1f", "shasum": "" }, "require": { @@ -2018,7 +2018,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-17T17:07:04+00:00" + "time": "2026-02-24T14:35:15+00:00" }, { "name": "laravel/prompts", @@ -2081,16 +2081,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v2.0.9", + "version": "v2.0.10", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "8f631589ab07b7b52fead814965f5a800459cb3e" + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/8f631589ab07b7b52fead814965f5a800459cb3e", - "reference": "8f631589ab07b7b52fead814965f5a800459cb3e", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669", + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669", "shasum": "" }, "require": { @@ -2138,36 +2138,36 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2026-02-03T06:55:34+00:00" + "time": "2026-02-20T19:59:49+00:00" }, { "name": "laravel/socialite", - "version": "v5.24.2", + "version": "v5.24.3", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613" + "reference": "0feb62267e7b8abc68593ca37639ad302728c129" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613", - "reference": "5cea2eebf11ca4bc6c2f20495c82a70a9b3d1613", + "url": "https://api.github.com/repos/laravel/socialite/zipball/0feb62267e7b8abc68593ca37639ad302728c129", + "reference": "0feb62267e7b8abc68593ca37639ad302728c129", "shasum": "" }, "require": { "ext-json": "*", "firebase/php-jwt": "^6.4|^7.0", "guzzlehttp/guzzle": "^6.0|^7.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", "league/oauth1-client": "^1.11", "php": "^7.2|^8.0", "phpseclib/phpseclib": "^3.0" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.18|^5.20|^6.47|^7.55|^8.36|^9.15|^10.8", + "orchestra/testbench": "^4.18|^5.20|^6.47|^7.55|^8.36|^9.15|^10.8|^11.0", "phpstan/phpstan": "^1.12.23", "phpunit/phpunit": "^8.0|^9.3|^10.4|^11.5|^12.0" }, @@ -2210,7 +2210,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2026-01-10T16:07:28+00:00" + "time": "2026-02-21T13:32:50+00:00" }, { "name": "laravel/tinker", @@ -2469,16 +2469,16 @@ }, { "name": "league/flysystem", - "version": "3.31.0", + "version": "3.32.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff" + "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1717e0b3642b0df65ecb0cc89cdd99fa840672ff", - "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/254b1595b16b22dbddaaef9ed6ca9fdac4956725", + "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725", "shasum": "" }, "require": { @@ -2546,22 +2546,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.31.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.32.0" }, - "time": "2026-01-23T15:38:47+00:00" + "time": "2026-02-25T17:01:41+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.31.0", + "version": "3.32.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "e36a2bc60b06332c92e4435047797ded352b446f" + "reference": "a1979df7c9784d334ea6df356aed3d18ac6673d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/e36a2bc60b06332c92e4435047797ded352b446f", - "reference": "e36a2bc60b06332c92e4435047797ded352b446f", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/a1979df7c9784d334ea6df356aed3d18ac6673d0", + "reference": "a1979df7c9784d334ea6df356aed3d18ac6673d0", "shasum": "" }, "require": { @@ -2601,9 +2601,9 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.31.0" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.32.0" }, - "time": "2026-01-23T15:30:45+00:00" + "time": "2026-02-25T16:46:44+00:00" }, { "name": "league/flysystem-local", @@ -3465,16 +3465,16 @@ }, { "name": "nette/schema", - "version": "v1.3.4", + "version": "v1.3.5", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7" + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/086497a2f34b82fede9b5a41cc8e131d087cd8f7", - "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7", + "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", "shasum": "" }, "require": { @@ -3482,8 +3482,10 @@ "php": "8.1 - 8.5" }, "require-dev": { + "nette/phpstan-rules": "^1.0", "nette/tester": "^2.6", - "phpstan/phpstan": "^2.0@stable", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", "tracy/tracy": "^2.8" }, "type": "library", @@ -3524,9 +3526,9 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.4" + "source": "https://github.com/nette/schema/tree/v1.3.5" }, - "time": "2026-02-08T02:54:00+00:00" + "time": "2026-02-23T03:47:12+00:00" }, { "name": "nette/utils", @@ -4186,16 +4188,16 @@ }, { "name": "predis/predis", - "version": "v3.4.0", + "version": "v3.4.1", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "1183f5732e6b10efd33f64984a96726eaecb59aa" + "reference": "0850f2f36ee179f0ff96c92c750e1366c6cd754c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/1183f5732e6b10efd33f64984a96726eaecb59aa", - "reference": "1183f5732e6b10efd33f64984a96726eaecb59aa", + "url": "https://api.github.com/repos/predis/predis/zipball/0850f2f36ee179f0ff96c92c750e1366c6cd754c", + "reference": "0850f2f36ee179f0ff96c92c750e1366c6cd754c", "shasum": "" }, "require": { @@ -4237,7 +4239,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v3.4.0" + "source": "https://github.com/predis/predis/tree/v3.4.1" }, "funding": [ { @@ -4245,7 +4247,7 @@ "type": "github" } ], - "time": "2026-02-11T17:30:28+00:00" + "time": "2026-02-23T19:51:21+00:00" }, { "name": "psr/clock", @@ -4980,33 +4982,35 @@ }, { "name": "sabberworm/php-css-parser", - "version": "v9.1.0", + "version": "v9.2.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb" + "reference": "59373045e11ad47b5c18fc615feee0219e42f6d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb", - "reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/59373045e11ad47b5c18fc615feee0219e42f6d3", + "reference": "59373045e11ad47b5c18fc615feee0219e42f6d3", "shasum": "" }, "require": { "ext-iconv": "*", "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", - "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.3" + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.28 || 2.1.25", - "phpstan/phpstan-phpunit": "1.4.2 || 2.0.7", - "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6", - "phpunit/phpunit": "8.5.46", + "phpstan/phpstan": "1.12.32 || 2.1.32", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.8", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7", + "phpunit/phpunit": "8.5.52", "rawr/phpunit-data-provider": "3.3.1", - "rector/rector": "1.2.10 || 2.1.7", - "rector/type-perfect": "1.0.0 || 2.1.0" + "rector/rector": "1.2.10 || 2.2.8", + "rector/type-perfect": "1.0.0 || 2.1.0", + "squizlabs/php_codesniffer": "4.0.1", + "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" @@ -5014,10 +5018,14 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.2.x-dev" + "dev-main": "9.3.x-dev" } }, "autoload": { + "files": [ + "src/Rule/Rule.php", + "src/RuleSet/RuleContainer.php" + ], "psr-4": { "Sabberworm\\CSS\\": "src/" } @@ -5048,9 +5056,9 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.1.0" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.2.0" }, - "time": "2025-09-14T07:37:21+00:00" + "time": "2026-02-21T17:12:03+00:00" }, { "name": "socialiteproviders/discord", @@ -5500,16 +5508,16 @@ }, { "name": "symfony/console", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" + "reference": "6d643a93b47398599124022eb24d97c153c12f27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "url": "https://api.github.com/repos/symfony/console/zipball/6d643a93b47398599124022eb24d97c153c12f27", + "reference": "6d643a93b47398599124022eb24d97c153c12f27", "shasum": "" }, "require": { @@ -5574,7 +5582,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.4" + "source": "https://github.com/symfony/console/tree/v7.4.6" }, "funding": [ { @@ -5594,20 +5602,20 @@ "type": "tidelift" } ], - "time": "2026-01-13T11:36:38+00:00" + "time": "2026-02-25T17:02:47+00:00" }, { "name": "symfony/css-selector", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" + "reference": "2e7c52c647b406e2107dd867db424a4dbac91864" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/2e7c52c647b406e2107dd867db424a4dbac91864", + "reference": "2e7c52c647b406e2107dd867db424a4dbac91864", "shasum": "" }, "require": { @@ -5643,7 +5651,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.0" + "source": "https://github.com/symfony/css-selector/tree/v7.4.6" }, "funding": [ { @@ -5663,7 +5671,7 @@ "type": "tidelift" } ], - "time": "2025-10-30T13:39:42+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5977,16 +5985,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a" + "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3ebc794fa5315e59fd122561623c2e2e4280538e", + "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e", "shasum": "" }, "require": { @@ -6023,7 +6031,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.0" + "source": "https://github.com/symfony/filesystem/tree/v7.4.6" }, "funding": [ { @@ -6043,20 +6051,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/finder", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", "shasum": "" }, "require": { @@ -6091,7 +6099,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.5" + "source": "https://github.com/symfony/finder/tree/v7.4.6" }, "funding": [ { @@ -6111,20 +6119,20 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:07:59+00:00" + "time": "2026-01-29T09:40:50+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6" + "reference": "fd97d5e926e988a363cef56fbbf88c5c528e9065" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/446d0db2b1f21575f1284b74533e425096abdfb6", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/fd97d5e926e988a363cef56fbbf88c5c528e9065", + "reference": "fd97d5e926e988a363cef56fbbf88c5c528e9065", "shasum": "" }, "require": { @@ -6173,7 +6181,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.5" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.6" }, "funding": [ { @@ -6193,20 +6201,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-02-21T16:25:55+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a" + "reference": "002ac0cf4cd972a7fd0912dcd513a95e8a81ce83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/229eda477017f92bd2ce7615d06222ec0c19e82a", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/002ac0cf4cd972a7fd0912dcd513a95e8a81ce83", + "reference": "002ac0cf4cd972a7fd0912dcd513a95e8a81ce83", "shasum": "" }, "require": { @@ -6248,7 +6256,7 @@ "symfony/config": "^6.4|^7.0|^8.0", "symfony/console": "^6.4|^7.0|^8.0", "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", "symfony/dom-crawler": "^6.4|^7.0|^8.0", "symfony/expression-language": "^6.4|^7.0|^8.0", "symfony/finder": "^6.4|^7.0|^8.0", @@ -6292,7 +6300,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.5" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.6" }, "funding": [ { @@ -6312,20 +6320,20 @@ "type": "tidelift" } ], - "time": "2026-01-28T10:33:42+00:00" + "time": "2026-02-26T08:30:57+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6" + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/7b750074c40c694ceb34cb926d6dffee231c5cd6", - "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9", "shasum": "" }, "require": { @@ -6376,7 +6384,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.4" + "source": "https://github.com/symfony/mailer/tree/v7.4.6" }, "funding": [ { @@ -6396,20 +6404,20 @@ "type": "tidelift" } ], - "time": "2026-01-08T08:25:11+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/mime", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148" + "reference": "9fc881d95feae4c6c48678cb6372bd8a7ba04f5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b18c7e6e9eee1e19958138df10412f3c4c316148", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148", + "url": "https://api.github.com/repos/symfony/mime/zipball/9fc881d95feae4c6c48678cb6372bd8a7ba04f5f", + "reference": "9fc881d95feae4c6c48678cb6372bd8a7ba04f5f", "shasum": "" }, "require": { @@ -6420,7 +6428,7 @@ }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "symfony/mailer": "<6.4", "symfony/serializer": "<6.4.3|>7.0,<7.0.3" @@ -6428,7 +6436,7 @@ "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^5.2", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/property-access": "^6.4|^7.0|^8.0", @@ -6465,7 +6473,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.5" + "source": "https://github.com/symfony/mime/tree/v7.4.6" }, "funding": [ { @@ -6485,7 +6493,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T08:59:58+00:00" + "time": "2026-02-05T15:57:06+00:00" }, { "name": "symfony/polyfill-ctype", @@ -7383,16 +7391,16 @@ }, { "name": "symfony/routing", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "0798827fe2c79caeed41d70b680c2c3507d10147" + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/0798827fe2c79caeed41d70b680c2c3507d10147", - "reference": "0798827fe2c79caeed41d70b680c2c3507d10147", + "url": "https://api.github.com/repos/symfony/routing/zipball/238d749c56b804b31a9bf3e26519d93b65a60938", + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938", "shasum": "" }, "require": { @@ -7444,7 +7452,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.4" + "source": "https://github.com/symfony/routing/tree/v7.4.6" }, "funding": [ { @@ -7464,7 +7472,7 @@ "type": "tidelift" } ], - "time": "2026-01-12T12:19:02+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/service-contracts", @@ -7555,16 +7563,16 @@ }, { "name": "symfony/string", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", "shasum": "" }, "require": { @@ -7622,7 +7630,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.4" + "source": "https://github.com/symfony/string/tree/v7.4.6" }, "funding": [ { @@ -7642,20 +7650,20 @@ "type": "tidelift" } ], - "time": "2026-01-12T10:54:30+00:00" + "time": "2026-02-09T09:33:46+00:00" }, { "name": "symfony/translation", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "bfde13711f53f549e73b06d27b35a55207528877" + "reference": "1888cf064399868af3784b9e043240f1d89d25ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/bfde13711f53f549e73b06d27b35a55207528877", - "reference": "bfde13711f53f549e73b06d27b35a55207528877", + "url": "https://api.github.com/repos/symfony/translation/zipball/1888cf064399868af3784b9e043240f1d89d25ce", + "reference": "1888cf064399868af3784b9e043240f1d89d25ce", "shasum": "" }, "require": { @@ -7722,7 +7730,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.4" + "source": "https://github.com/symfony/translation/tree/v7.4.6" }, "funding": [ { @@ -7742,7 +7750,7 @@ "type": "tidelift" } ], - "time": "2026-01-13T10:40:19+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "symfony/translation-contracts", @@ -7906,16 +7914,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "0e4769b46a0c3c62390d124635ce59f66874b282" + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e4769b46a0c3c62390d124635ce59f66874b282", - "reference": "0e4769b46a0c3c62390d124635ce59f66874b282", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291", + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291", "shasum": "" }, "require": { @@ -7969,7 +7977,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.4" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.6" }, "funding": [ { @@ -7989,7 +7997,7 @@ "type": "tidelift" } ], - "time": "2026-01-01T22:13:48+00:00" + "time": "2026-02-15T10:53:20+00:00" }, { "name": "thecodingmachine/safe", @@ -9160,11 +9168,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.39", + "version": "2.1.40", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", - "reference": "c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", + "reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", "shasum": "" }, "require": { @@ -9209,7 +9217,7 @@ "type": "github" } ], - "time": "2026-02-11T14:48:56+00:00" + "time": "2026-02-23T15:04:35+00:00" }, { "name": "phpunit/php-code-coverage", @@ -10831,16 +10839,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "71fd6a82fc357c8b5de22f78b228acfc43dee965" + "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/71fd6a82fc357c8b5de22f78b228acfc43dee965", - "reference": "71fd6a82fc357c8b5de22f78b228acfc43dee965", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/487ba8fa43da9a8e6503fe939b45ecd96875410e", + "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e", "shasum": "" }, "require": { @@ -10879,7 +10887,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.4.4" + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.6" }, "funding": [ { @@ -10899,7 +10907,7 @@ "type": "tidelift" } ], - "time": "2026-01-05T08:47:25+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "theseer/tokenizer", From ec3dd856db6ed985240846179f92d9b966ceaa5e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 28 Feb 2026 18:46:05 +0000 Subject: [PATCH 069/204] Mail: Set domain for EHLO based upon the APP_URL For #5990 --- app/App/Providers/AppServiceProvider.php | 7 +++++++ tests/Unit/ConfigTest.php | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/app/App/Providers/AppServiceProvider.php b/app/App/Providers/AppServiceProvider.php index debba79446e..5264c0dccef 100644 --- a/app/App/Providers/AppServiceProvider.php +++ b/app/App/Providers/AppServiceProvider.php @@ -65,6 +65,13 @@ public function boot(): void URL::forceScheme($isHttps ? 'https' : 'http'); } + // Set SMTP mail driver to use a local domain matching the app domain, + // which helps avoid defaulting to a 127.0.0.1 domain + if ($appUrl) { + $hostName = parse_url($appUrl, PHP_URL_HOST) ?: null; + config()->set('mail.mailers.smtp.local_domain', $hostName); + } + // Allow longer string lengths after upgrade to utf8mb4 Schema::defaultStringLength(191); diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php index 9ed68c8bdfc..2e9190dfd36 100644 --- a/tests/Unit/ConfigTest.php +++ b/tests/Unit/ConfigTest.php @@ -122,6 +122,27 @@ public function test_mail_disable_ssl_verification_alters_mailer() }); } + public function test_app_url_changes_smtp_ehlo_host_on_mailer() + { + $getLocalDomain = function (): string { + /** @var EsmtpTransport $transport */ + $transport = Mail::mailer('smtp')->getSymfonyTransport(); + return $transport->getLocalDomain(); + }; + + $this->runWithEnv(['APP_URL' => ''], function () use ($getLocalDomain) { + $this->assertEquals('[127.0.0.1]', $getLocalDomain()); + }); + + $this->runWithEnv(['APP_URL' => 'https://example.com/cats/dogs'], function () use ($getLocalDomain) { + $this->assertEquals('example.com', $getLocalDomain()); + }); + + $this->runWithEnv(['APP_URL' => 'http://beans.cat.example.com'], function () use ($getLocalDomain) { + $this->assertEquals('beans.cat.example.com', $getLocalDomain()); + }); + } + public function test_non_null_mail_encryption_options_enforce_smtp_scheme() { $this->checkEnvConfigResult('MAIL_ENCRYPTION', 'tls', 'mail.mailers.smtp.require_tls', true); From f2f76a3c5658456a6d1d72174defed44c7f520cb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 6 Mar 2026 09:28:46 +0000 Subject: [PATCH 070/204] Modules: Improved install command based on testing - Updated output to be clearer - Added warning and confirmation to local install flow - Adjusted module folder name creation --- app/Console/Commands/InstallModuleCommand.php | 11 +++++++++-- app/Theming/ThemeModuleManager.php | 2 +- tests/Commands/InstallModuleCommandTest.php | 14 ++++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/Console/Commands/InstallModuleCommand.php b/app/Console/Commands/InstallModuleCommand.php index dc3c9363e3d..20252525df8 100644 --- a/app/Console/Commands/InstallModuleCommand.php +++ b/app/Console/Commands/InstallModuleCommand.php @@ -268,7 +268,7 @@ protected function getPathToZip(string $location): string|null if ($isRemote) { // Warning about fetching from source $host = parse_url($location, PHP_URL_HOST); - $this->warn("This will download a module from {$host}. Modules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources."); + $this->warn("\nThis will download a module from: {$host}\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources."); $trustHost = $this->confirm('Are you sure you trust this source?'); if (!$trustHost) { return null; @@ -286,13 +286,20 @@ protected function getPathToZip(string $location): string|null return $this->downloadModuleFile($location); } - // Validate file and get full location + // Validate the file and get the full location $zipPath = realpath($location); + if (!$zipPath || !is_file($zipPath)) { $this->error("ERROR: Module file not found at {$location}"); return null; } + $this->warn("\nThis will install a module from: {$zipPath}\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources."); + $trustHost = $this->confirm('Are you sure you want to install this module?'); + if (!$trustHost) { + return null; + } + return $zipPath; } diff --git a/app/Theming/ThemeModuleManager.php b/app/Theming/ThemeModuleManager.php index 900063d47e1..86362a2f321 100644 --- a/app/Theming/ThemeModuleManager.php +++ b/app/Theming/ThemeModuleManager.php @@ -44,7 +44,7 @@ public function deleteModuleFolder(string $moduleFolderName): void */ public function addFromZip(string $name, ThemeModuleZip $zip): ThemeModule { - $baseFolderName = Str::limit(Str::slug($name), 20); + $baseFolderName = Str::limit(Str::slug($name), 40, ''); $folderName = $baseFolderName; while (!$baseFolderName || file_exists($this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName)) { $folderName = ($baseFolderName ?: 'mod') . '-' . Str::random(4); diff --git a/tests/Commands/InstallModuleCommandTest.php b/tests/Commands/InstallModuleCommandTest.php index 0872efc3f26..8ffc4ead3a0 100644 --- a/tests/Commands/InstallModuleCommandTest.php +++ b/tests/Commands/InstallModuleCommandTest.php @@ -15,6 +15,8 @@ public function test_local_module_install_with_active_theme() $zip = $this->getModuleZipPath(); $expectedInstallPath = theme_path('modules/test-module'); $this->artisan('bookstack:install-module', ['location' => $zip]) + ->expectsOutput("\nThis will install a module from: {$zip}\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.") + ->expectsConfirmation('Are you sure you want to install this module?', 'yes') ->expectsOutput('Module "Test Module" (v1.0.0) successfully installed!') ->expectsOutput("Install location: {$expectedInstallPath}") ->assertExitCode(0); @@ -35,7 +37,7 @@ public function test_remote_module_install_with_active_theme() $expectedInstallPath = theme_path('modules/test-module'); $this->artisan('bookstack:install-module', ['location' => 'https://example.com/test-module.zip']) - ->expectsOutput("This will download a module from example.com. Modules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.") + ->expectsOutput("\nThis will download a module from: example.com\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.") ->expectsConfirmation('Are you sure you trust this source?', 'yes') ->expectsOutput('Module "Test Module" (v1.0.0) successfully installed!') ->expectsOutput("Install location: {$expectedInstallPath}") @@ -61,7 +63,7 @@ public function test_remote_http_module_warns_and_prompts_users() $expectedInstallPath = theme_path('modules/test-module'); $this->artisan('bookstack:install-module', ['location' => 'http://example.com/test-module.zip']) - ->expectsOutput("This will download a module from example.com. Modules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.") + ->expectsOutput("\nThis will download a module from: example.com\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.") ->expectsConfirmation('Are you sure you trust this source?', 'yes') ->expectsOutput("You are downloading a module from an insecure HTTP source.\nWe recommend only using HTTPS sources to avoid various security risks.") ->expectsConfirmation('Are you sure you want to continue without HTTPS?', 'yes') @@ -142,6 +144,7 @@ public function test_run_with_invalid_zip_has_early_exit() file_put_contents($zip, 'invalid zip'); $this->artisan('bookstack:install-module', ['location' => $zip]) + ->expectsConfirmation('Are you sure you want to install this module?', 'yes') ->expectsOutput("ERROR: Cannot open ZIP file at {$zip}") ->assertExitCode(1); } @@ -153,6 +156,7 @@ public function test_run_with_large_zip_has_early_exit() ]); $this->artisan('bookstack:install-module', ['location' => $zip]) + ->expectsConfirmation('Are you sure you want to install this module?', 'yes') ->expectsOutput("ERROR: Module ZIP file contents are too large. Maximum size is 50MB") ->assertExitCode(1); } @@ -166,6 +170,7 @@ public function test_run_with_invalid_module_data_has_early_exit() ]); $this->artisan('bookstack:install-module', ['location' => $zip]) + ->expectsConfirmation('Are you sure you want to install this module?', 'yes') ->expectsOutput("ERROR: Failed to read module metadata with error: Module in folder \"_temp\" has an invalid 'version' format. Expected semantic version format like '1.0.0' or 'v1.0.0'") ->assertExitCode(1); } @@ -177,6 +182,7 @@ public function test_local_module_install_without_active_theme_can_setup_theme_f File::deleteDirectory($expectedThemePath); $this->artisan('bookstack:install-module', ['location' => $zip]) + ->expectsConfirmation('Are you sure you want to install this module?', 'yes') ->expectsConfirmation('No active theme folder found, would you like to create one?', 'yes') ->expectsOutput("Created theme folder at {$expectedThemePath}") ->expectsOutput("You will need to set APP_THEME=custom in your BookStack env configuration to enable this theme!") @@ -195,6 +201,7 @@ public function test_local_module_install_with_active_theme_and_conflicting_modu File::put(theme_path('modules'), '{}'); $this->artisan('bookstack:install-module', ['location' => $zip]) + ->expectsConfirmation('Are you sure you want to install this module?', 'yes') ->expectsOutput("ERROR: Cannot create a modules folder, file already exists at " . theme_path('modules')) ->assertExitCode(1); }); @@ -207,6 +214,7 @@ public function test_single_existing_module_with_same_name_replace() $new = $this->getModuleZipPath(['name' => 'Test Module', 'description' => '', 'version' => '2.0.0']); $this->artisan('bookstack:install-module', ['location' => $new]) + ->expectsConfirmation('Are you sure you want to install this module?', 'yes') ->expectsOutput('The following modules already exist with the same name:') ->expectsOutput('Test Module (test-module:v1.0.0) - cat') ->expectsChoice('What would you like to do?', 'Replace existing module', ['Cancel module install', 'Add alongside existing module', 'Replace existing module']) @@ -226,6 +234,7 @@ public function test_single_existing_module_with_same_name_cancel() $new = $this->getModuleZipPath(['name' => 'Test Module', 'description' => '', 'version' => '2.0.0']); $this->artisan('bookstack:install-module', ['location' => $new]) + ->expectsConfirmation('Are you sure you want to install this module?', 'yes') ->expectsOutput('The following modules already exist with the same name:') ->expectsOutput('Test Module (test-module:v1.0.0) - cat') ->expectsChoice('What would you like to do?', 'Cancel module install', ['Cancel module install', 'Add alongside existing module', 'Replace existing module']) @@ -244,6 +253,7 @@ public function test_single_existing_module_with_same_name_add() $new = $this->getModuleZipPath(['name' => 'Test Module', 'description' => '', 'version' => '2.0.0']); $this->artisan('bookstack:install-module', ['location' => $new]) + ->expectsConfirmation('Are you sure you want to install this module?', 'yes') ->expectsOutput('The following modules already exist with the same name:') ->expectsOutput('Test Module (test-module:v1.0.0) - cat') ->expectsChoice('What would you like to do?', 'Add alongside existing module', ['Cancel module install', 'Add alongside existing module', 'Replace existing module']) From 7d0237c798320e7383c1b5a33c23210f2f062f96 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 6 Mar 2026 10:25:27 +0000 Subject: [PATCH 071/204] NPM Deps: Updated package versions Fixed SCSS if deprecations Fixed new eslint detected issues --- package-lock.json | 2158 +++++++---------- package.json | 26 +- resources/js/wysiwyg-tinymce/plugin-drawio.js | 4 +- resources/sass/_mixins.scss | 12 +- 4 files changed, 954 insertions(+), 1246 deletions(-) diff --git a/package-lock.json b/package-lock.json index e8a1493d42f..b6508f1e9e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,62 +5,48 @@ "packages": { "": { "dependencies": { - "@codemirror/commands": "^6.10.0", + "@codemirror/commands": "^6.10.2", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.11", - "@codemirror/lang-javascript": "^6.2.4", + "@codemirror/lang-javascript": "^6.2.5", "@codemirror/lang-json": "^6.0.2", "@codemirror/lang-markdown": "^6.5.0", "@codemirror/lang-php": "^6.0.2", "@codemirror/lang-xml": "^6.1.0", - "@codemirror/language": "^6.11.3", + "@codemirror/language": "^6.12.2", "@codemirror/legacy-modes": "^6.5.2", - "@codemirror/state": "^6.5.2", + "@codemirror/state": "^6.5.4", "@codemirror/theme-one-dark": "^6.1.3", - "@codemirror/view": "^6.38.8", + "@codemirror/view": "^6.39.16", "@lezer/highlight": "^1.2.3", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", "@types/jest": "^30.0.0", "codemirror": "^6.0.2", "idb-keyval": "^6.2.2", - "markdown-it": "^14.1.0", + "markdown-it": "^14.1.1", "markdown-it-task-lists": "^2.1.1", "snabbdom": "^3.6.3", - "sortablejs": "^1.15.6" + "sortablejs": "^1.15.7" }, "devDependencies": { - "@eslint/js": "^9.39.1", + "@eslint/js": "^10.0.1", "@lezer/generator": "^1.8.0", "@types/markdown-it": "^14.1.2", "@types/sortablejs": "^1.15.9", "chokidar-cli": "^3.0", - "esbuild": "^0.27.0", - "eslint": "^9.39.1", - "eslint-plugin-import": "^2.32.0", + "esbuild": "^0.27.3", + "eslint": "^10.0.2", + "globals": "^17.4.0", "jest": "^30.2.0", "jest-environment-jsdom": "^30.2.0", "npm-run-all": "^4.1.5", - "sass": "^1.94.2", - "ts-jest": "^29.4.5", + "sass": "^1.97.3", + "ts-jest": "^29.4.6", "ts-node": "^10.9.2", "typescript": "5.9.*" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@asamuzakjp/css-color": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", @@ -83,12 +69,12 @@ "license": "ISC" }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -97,9 +83,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", - "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -107,22 +93,22 @@ } }, "node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -138,14 +124,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -155,13 +141,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -182,29 +168,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -214,9 +200,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -234,9 +220,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -253,27 +239,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -338,13 +324,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -380,13 +366,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -506,13 +492,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -522,33 +508,33 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -556,14 +542,14 @@ } }, "node_modules/@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -577,9 +563,9 @@ "license": "MIT" }, "node_modules/@codemirror/autocomplete": { - "version": "6.18.6", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz", - "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==", + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz", + "integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==", "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", @@ -589,9 +575,9 @@ } }, "node_modules/@codemirror/commands": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz", - "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.2.tgz", + "integrity": "sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==", "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", @@ -631,9 +617,9 @@ } }, "node_modules/@codemirror/lang-javascript": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", - "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", @@ -698,14 +684,14 @@ } }, "node_modules/@codemirror/language": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", - "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz", + "integrity": "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", - "@lezer/common": "^1.1.0", + "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" @@ -721,9 +707,9 @@ } }, "node_modules/@codemirror/lint": { - "version": "6.8.5", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", - "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz", + "integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", @@ -732,20 +718,20 @@ } }, "node_modules/@codemirror/search": { - "version": "6.5.11", - "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", - "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz", + "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", + "@codemirror/view": "^6.37.0", "crelt": "^1.0.5" } }, "node_modules/@codemirror/state": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", - "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz", + "integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==", "license": "MIT", "dependencies": { "@marijn/find-cluster-break": "^1.0.0" @@ -764,9 +750,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.38.8", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz", - "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==", + "version": "6.39.16", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.16.tgz", + "integrity": "sha512-m6S22fFpKtOWhq8HuhzsI1WzUP/hB9THbDj0Tl5KX4gbO6Y91hwBl7Yky33NdvB6IffuRFiBxf1R8kJMyXmA4Q==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.5.0", @@ -915,9 +901,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", "dev": true, "license": "MIT", "optional": true, @@ -927,9 +913,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "dev": true, "license": "MIT", "optional": true, @@ -949,9 +935,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", - "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", "cpu": [ "ppc64" ], @@ -966,9 +952,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", - "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", "cpu": [ "arm" ], @@ -983,9 +969,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", - "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", "cpu": [ "arm64" ], @@ -1000,9 +986,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", - "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", "cpu": [ "x64" ], @@ -1017,9 +1003,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", - "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", "cpu": [ "arm64" ], @@ -1034,9 +1020,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", - "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", "cpu": [ "x64" ], @@ -1051,9 +1037,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", - "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", "cpu": [ "arm64" ], @@ -1068,9 +1054,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", - "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", "cpu": [ "x64" ], @@ -1085,9 +1071,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", - "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "cpu": [ "arm" ], @@ -1102,9 +1088,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", - "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", "cpu": [ "arm64" ], @@ -1119,9 +1105,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", - "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", "cpu": [ "ia32" ], @@ -1136,9 +1122,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", - "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", "cpu": [ "loong64" ], @@ -1153,9 +1139,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", - "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", "cpu": [ "mips64el" ], @@ -1170,9 +1156,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", - "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", "cpu": [ "ppc64" ], @@ -1187,9 +1173,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", - "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", "cpu": [ "riscv64" ], @@ -1204,9 +1190,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", - "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", "cpu": [ "s390x" ], @@ -1221,9 +1207,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", - "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", "cpu": [ "x64" ], @@ -1238,9 +1224,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", - "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", "cpu": [ "arm64" ], @@ -1255,9 +1241,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", - "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", "cpu": [ "x64" ], @@ -1272,9 +1258,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", - "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", "cpu": [ "arm64" ], @@ -1289,9 +1275,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", - "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", "cpu": [ "x64" ], @@ -1306,9 +1292,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", - "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", "cpu": [ "arm64" ], @@ -1323,9 +1309,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", - "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", "cpu": [ "x64" ], @@ -1340,9 +1326,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", - "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", "cpu": [ "arm64" ], @@ -1357,9 +1343,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", - "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", "cpu": [ "ia32" ], @@ -1374,9 +1360,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", - "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", "cpu": [ "x64" ], @@ -1391,9 +1377,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1423,9 +1409,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -1433,105 +1419,89 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", + "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.7", + "@eslint/object-schema": "^3.0.2", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^10.2.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", + "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0" + "@eslint/core": "^1.1.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", + "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", + "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", + "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0", + "@eslint/core": "^1.1.0", "levn": "^0.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@humanfs/core": { @@ -1545,33 +1515,19 @@ } }, "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1618,62 +1574,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1691,16 +1591,6 @@ "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -1715,20 +1605,6 @@ "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -1771,16 +1647,6 @@ "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -2156,9 +2022,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { @@ -2166,6 +2032,17 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -2177,16 +2054,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -2195,15 +2072,15 @@ } }, "node_modules/@lezer/common": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz", - "integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz", + "integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==", "license": "MIT" }, "node_modules/@lezer/css": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz", - "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.1.tgz", + "integrity": "sha512-PYAKeUVBo3HFThruRyp/iK91SwiZJnzXh8QzkQlwijB5y+N5iB28+iLk78o2zmKqqV0uolNhCwFqB8LA7b0Svg==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -2235,9 +2112,9 @@ } }, "node_modules/@lezer/html": { - "version": "1.3.12", - "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz", - "integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==", + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -2246,9 +2123,9 @@ } }, "node_modules/@lezer/javascript": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz", - "integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -2268,28 +2145,28 @@ } }, "node_modules/@lezer/lr": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", - "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz", + "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==", "license": "MIT", "dependencies": { "@lezer/common": "^1.0.0" } }, "node_modules/@lezer/markdown": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.4.3.tgz", - "integrity": "sha512-kfw+2uMrQ/wy/+ONfrH83OkdFNM0ye5Xq96cLlaCy7h5UT9FO54DU4oRoIc0CSBh5NWmWuiIJA7NGLMJbQ+Oxg==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.3.tgz", + "integrity": "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==", "license": "MIT", "dependencies": { - "@lezer/common": "^1.0.0", + "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0" } }, "node_modules/@lezer/php": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.4.tgz", - "integrity": "sha512-D2dJ0t8Z28/G1guztRczMFvPDUqzeMLSQbdWQmaiHV7urc8NlEOnjYk9UrZ531OcLiRxD4Ihcbv7AsDpNKDRaQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.5.tgz", + "integrity": "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -2328,18 +2205,18 @@ } }, "node_modules/@parcel/watcher": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.3", "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">= 10.0.0" @@ -2349,25 +2226,25 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" } }, "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", "cpu": [ "arm64" ], @@ -2386,9 +2263,9 @@ } }, "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", - "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", "cpu": [ "arm64" ], @@ -2407,9 +2284,9 @@ } }, "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", "cpu": [ "x64" ], @@ -2428,9 +2305,9 @@ } }, "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", "cpu": [ "x64" ], @@ -2449,9 +2326,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", "cpu": [ "arm" ], @@ -2470,9 +2347,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", "cpu": [ "arm" ], @@ -2491,9 +2368,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", "cpu": [ "arm64" ], @@ -2512,9 +2389,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", "cpu": [ "arm64" ], @@ -2533,9 +2410,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", - "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", "cpu": [ "x64" ], @@ -2554,9 +2431,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", "cpu": [ "x64" ], @@ -2575,9 +2452,9 @@ } }, "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", "cpu": [ "arm64" ], @@ -2596,9 +2473,9 @@ } }, "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", "cpu": [ "ia32" ], @@ -2617,9 +2494,9 @@ } }, "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", "cpu": [ "x64" ], @@ -2637,6 +2514,20 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2661,17 +2552,10 @@ "url": "https://opencollective.com/pkgr" } }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" - }, "node_modules/@sinclair/typebox": { - "version": "0.34.41", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", - "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", "license": "MIT" }, "node_modules/@sinonjs/commons": { @@ -2712,9 +2596,9 @@ } }, "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", "dev": true, "license": "MIT" }, @@ -2795,6 +2679,13 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2855,13 +2746,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -2888,12 +2772,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", - "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", + "version": "25.3.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz", + "integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==", "license": "MIT", "dependencies": { - "undici-types": "~7.8.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/sortablejs": { @@ -2917,9 +2801,9 @@ "license": "MIT" }, "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -3208,9 +3092,9 @@ ] }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -3231,9 +3115,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", "dev": true, "license": "MIT", "dependencies": { @@ -3254,9 +3138,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -3336,10 +3220,14 @@ "license": "MIT" }, "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", @@ -3358,21 +3246,20 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, "license": "MIT", "dependencies": { + "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", - "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { "node": ">= 0.4" @@ -3381,39 +3268,24 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" - }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" + "possible-typed-array-names": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -3422,87 +3294,20 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "node_modules/babel-jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/babel-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", - "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "30.2.0", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" + "@jest/transform": "30.2.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -3589,11 +3394,27 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -3609,14 +3430,16 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -3632,9 +3455,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -3652,10 +3475,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -3765,9 +3589,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001727", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", - "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "version": "1.0.30001777", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", + "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", "dev": true, "funding": [ { @@ -3856,9 +3680,9 @@ } }, "node_modules/ci-info": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", - "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "funding": [ { "type": "github", @@ -3871,9 +3695,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", - "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", "dev": true, "license": "MIT" }, @@ -3899,6 +3723,68 @@ "node": ">=6" } }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/cliui/node_modules/strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -3912,6 +3798,21 @@ "node": ">=6" } }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -4088,9 +3989,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -4123,9 +4024,9 @@ "license": "MIT" }, "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -4191,17 +4092,14 @@ } }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "optional": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/detect-newline": { @@ -4215,28 +4113,15 @@ } }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } }, - "node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -4260,9 +4145,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.190", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.190.tgz", - "integrity": "sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==", + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", "dev": true, "license": "ISC" }, @@ -4280,9 +4165,9 @@ } }, "node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, @@ -4299,9 +4184,9 @@ } }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4309,9 +4194,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "dev": true, "license": "MIT", "dependencies": { @@ -4426,19 +4311,6 @@ "node": ">= 0.4" } }, - "node_modules/es-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/es-to-primitive": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", @@ -4458,9 +4330,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", - "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4471,32 +4343,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.0", - "@esbuild/android-arm": "0.27.0", - "@esbuild/android-arm64": "0.27.0", - "@esbuild/android-x64": "0.27.0", - "@esbuild/darwin-arm64": "0.27.0", - "@esbuild/darwin-x64": "0.27.0", - "@esbuild/freebsd-arm64": "0.27.0", - "@esbuild/freebsd-x64": "0.27.0", - "@esbuild/linux-arm": "0.27.0", - "@esbuild/linux-arm64": "0.27.0", - "@esbuild/linux-ia32": "0.27.0", - "@esbuild/linux-loong64": "0.27.0", - "@esbuild/linux-mips64el": "0.27.0", - "@esbuild/linux-ppc64": "0.27.0", - "@esbuild/linux-riscv64": "0.27.0", - "@esbuild/linux-s390x": "0.27.0", - "@esbuild/linux-x64": "0.27.0", - "@esbuild/netbsd-arm64": "0.27.0", - "@esbuild/netbsd-x64": "0.27.0", - "@esbuild/openbsd-arm64": "0.27.0", - "@esbuild/openbsd-x64": "0.27.0", - "@esbuild/openharmony-arm64": "0.27.0", - "@esbuild/sunos-x64": "0.27.0", - "@esbuild/win32-arm64": "0.27.0", - "@esbuild/win32-ia32": "0.27.0", - "@esbuild/win32-x64": "0.27.0" + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" } }, "node_modules/escalade": { @@ -4523,33 +4395,30 @@ } }, "node_modules/eslint": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", - "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz", + "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", - "@eslint/plugin-kit": "^0.4.1", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.2", + "@eslint/config-helpers": "^0.5.2", + "@eslint/core": "^1.1.0", + "@eslint/plugin-kit": "^0.6.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", + "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", + "eslint-scope": "^9.1.1", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.1.1", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -4559,8 +4428,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^10.2.1", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -4568,7 +4436,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -4582,125 +4450,33 @@ } } }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", - "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", - "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.9", - "array.prototype.findlastindex": "^1.2.6", - "array.prototype.flat": "^1.3.3", - "array.prototype.flatmap": "^1.3.3", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.1", - "hasown": "^2.0.2", - "is-core-module": "^2.16.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.1", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.9", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", + "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4720,18 +4496,18 @@ } }, "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", + "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4752,9 +4528,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4943,9 +4719,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", + "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", "dev": true, "license": "ISC" }, @@ -5045,6 +4821,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5149,6 +4935,7 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -5179,6 +4966,13 @@ "node": ">= 6" } }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -5190,13 +4984,13 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -5206,9 +5000,9 @@ } }, "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", + "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", "dev": true, "license": "MIT", "engines": { @@ -5464,29 +5258,12 @@ } }, "node_modules/immutable": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", - "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", "dev": true, "license": "MIT" }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -5733,13 +5510,13 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/is-generator-fn": { @@ -5753,14 +5530,15 @@ } }, "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" }, @@ -6043,9 +5821,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -6254,16 +6032,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jest-cli/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-cli/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -6731,9 +6499,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -6863,13 +6631,14 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -7096,13 +6865,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", @@ -7137,9 +6899,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -7167,9 +6929,9 @@ } }, "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1", @@ -7189,6 +6951,12 @@ "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", "license": "ISC" }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7245,16 +7013,19 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -7268,11 +7039,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -7337,9 +7108,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "dev": true, "license": "MIT" }, @@ -7415,6 +7186,24 @@ "node": ">=4" } }, + "node_modules/npm-run-all/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-all/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/npm-run-all/node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -7484,6 +7273,19 @@ "node": ">=4" } }, + "node_modules/npm-run-all/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/npm-run-all/node_modules/path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -7567,9 +7369,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.22", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz", - "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", "dev": true, "license": "MIT" }, @@ -7589,79 +7391,26 @@ "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } }, - "node_modules/object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -7781,19 +7530,6 @@ "dev": true, "license": "BlueOak-1.0.0" }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -8211,13 +7947,13 @@ "license": "ISC" }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -8244,7 +7980,7 @@ "node": ">=8" } }, - "node_modules/resolve-cwd/node_modules/resolve-from": { + "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", @@ -8254,16 +7990,6 @@ "node": ">=8" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/rrweb-cssom": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", @@ -8334,9 +8060,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.94.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.94.2.tgz", - "integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==", + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", "dev": true, "license": "MIT", "dependencies": { @@ -8607,9 +8333,9 @@ } }, "node_modules/sortablejs": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz", - "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.7.tgz", + "integrity": "sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==", "license": "MIT" }, "node_modules/source-map": { @@ -8673,9 +8399,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", "dev": true, "license": "CC0-1.0" }, @@ -8759,18 +8485,21 @@ } }, "node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string-width-cjs": { @@ -8806,16 +8535,6 @@ "dev": true, "license": "MIT" }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -8829,29 +8548,6 @@ "node": ">=8" } }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/string.prototype.padend": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", @@ -8931,13 +8627,13 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -9004,9 +8700,9 @@ } }, "node_modules/style-mod": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", - "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", "license": "MIT" }, "node_modules/supports-color": { @@ -9042,9 +8738,9 @@ "license": "MIT" }, "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9072,11 +8768,29 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/test-exclude/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -9094,6 +8808,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/tldts": { "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", @@ -9160,9 +8887,9 @@ } }, "node_modules/ts-jest": { - "version": "29.4.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz", - "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==", + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", "dev": true, "license": "MIT", "dependencies": { @@ -9213,9 +8940,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -9282,42 +9009,6 @@ } } }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -9441,9 +9132,9 @@ } }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -9494,9 +9185,9 @@ } }, "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/unrs-resolver": { @@ -9535,9 +9226,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -9651,6 +9342,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", "dev": true, "license": "MIT", "dependencies": { @@ -9775,9 +9467,9 @@ "license": "ISC" }, "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -9814,18 +9506,21 @@ "license": "MIT" }, "node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/wrap-ansi-cjs": { @@ -9864,16 +9559,6 @@ "dev": true, "license": "MIT" }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -9902,57 +9587,17 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, "engines": { - "node": ">=4" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" + "node": ">=12" }, - "engines": { - "node": ">=6" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/wrappy": { @@ -9977,9 +9622,9 @@ } }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "dev": true, "license": "MIT", "engines": { @@ -10058,6 +9703,23 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true, + "license": "MIT" + }, "node_modules/yargs/node_modules/find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -10071,6 +9733,16 @@ "node": ">=6" } }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/yargs/node_modules/locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -10124,6 +9796,34 @@ "node": ">=4" } }, + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/yargs/node_modules/yargs-parser": { "version": "13.1.2", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", diff --git a/package.json b/package.json index 624ff876a3a..45c275863b3 100644 --- a/package.json +++ b/package.json @@ -18,45 +18,45 @@ "test": "jest" }, "devDependencies": { - "@eslint/js": "^9.39.1", + "@eslint/js": "^10.0.1", "@lezer/generator": "^1.8.0", "@types/markdown-it": "^14.1.2", "@types/sortablejs": "^1.15.9", "chokidar-cli": "^3.0", - "esbuild": "^0.27.0", - "eslint": "^9.39.1", - "eslint-plugin-import": "^2.32.0", + "esbuild": "^0.27.3", + "eslint": "^10.0.2", + "globals": "^17.4.0", "jest": "^30.2.0", "jest-environment-jsdom": "^30.2.0", "npm-run-all": "^4.1.5", - "sass": "^1.94.2", - "ts-jest": "^29.4.5", + "sass": "^1.97.3", + "ts-jest": "^29.4.6", "ts-node": "^10.9.2", "typescript": "5.9.*" }, "dependencies": { - "@codemirror/commands": "^6.10.0", + "@codemirror/commands": "^6.10.2", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.11", - "@codemirror/lang-javascript": "^6.2.4", + "@codemirror/lang-javascript": "^6.2.5", "@codemirror/lang-json": "^6.0.2", "@codemirror/lang-markdown": "^6.5.0", "@codemirror/lang-php": "^6.0.2", "@codemirror/lang-xml": "^6.1.0", - "@codemirror/language": "^6.11.3", + "@codemirror/language": "^6.12.2", "@codemirror/legacy-modes": "^6.5.2", - "@codemirror/state": "^6.5.2", + "@codemirror/state": "^6.5.4", "@codemirror/theme-one-dark": "^6.1.3", - "@codemirror/view": "^6.38.8", + "@codemirror/view": "^6.39.16", "@lezer/highlight": "^1.2.3", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", "@types/jest": "^30.0.0", "codemirror": "^6.0.2", "idb-keyval": "^6.2.2", - "markdown-it": "^14.1.0", + "markdown-it": "^14.1.1", "markdown-it-task-lists": "^2.1.1", "snabbdom": "^3.6.3", - "sortablejs": "^1.15.6" + "sortablejs": "^1.15.7" } } diff --git a/resources/js/wysiwyg-tinymce/plugin-drawio.js b/resources/js/wysiwyg-tinymce/plugin-drawio.js index 197c50b0e44..ad0e01800ef 100644 --- a/resources/js/wysiwyg-tinymce/plugin-drawio.js +++ b/resources/js/wysiwyg-tinymce/plugin-drawio.js @@ -57,7 +57,7 @@ async function updateContent(pngData) { }); } catch (err) { handleUploadError(err); - throw new Error(`Failed to save image with error: ${err}`); + throw new Error(`Failed to save image with error: ${err}`, {cause: err}); } return; } @@ -78,7 +78,7 @@ async function updateContent(pngData) { } catch (err) { pageEditor.dom.remove(wrapId); handleUploadError(err); - throw new Error(`Failed to save image with error: ${err}`); + throw new Error(`Failed to save image with error: ${err}`, {cause: err}); } } diff --git a/resources/sass/_mixins.scss b/resources/sass/_mixins.scss index fc508600839..e9f2db69cd9 100644 --- a/resources/sass/_mixins.scss +++ b/resources/sass/_mixins.scss @@ -35,9 +35,17 @@ // Define a property for both light and dark mode @mixin lightDark($prop, $light, $dark, $important: false) { - #{$prop}: if($important, $light !important, $light); + @if($important) { + #{$prop}: $light !important; + } @else { + #{$prop}: $light; + } html.dark-mode & { - #{$prop}: if($important, $dark !important, $dark); + @if($important) { + #{$prop}: $dark !important; + } @else { + #{$prop}: $dark; + } } } From 27240be499ef02cd04c703778f1285939992501f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 6 Mar 2026 12:40:22 +0000 Subject: [PATCH 072/204] Theme System: Added new page-content focused events Closes #6049 --- app/Entities/Tools/PageContent.php | 32 +++++++++-- app/Theming/ThemeEvents.php | 25 +++++++++ tests/Theme/LogicalThemeEventsTest.php | 76 ++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 6 deletions(-) diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index 4f72e7c490d..8d89a86cff4 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -39,7 +39,14 @@ public function __construct( public function setNewHTML(string $html, User $updater): void { $html = $this->extractBase64ImagesFromHtml($html, $updater); - $this->page->html = $this->formatHtml($html); + $html = $this->formatHtml($html); + + $themeResult = Theme::dispatch(ThemeEvents::PAGE_CONTENT_PRE_STORE, $html, $this->page); + if (is_string($themeResult)) { + $html = $themeResult; + } + + $this->page->html = $html; $this->page->text = $this->toPlainText(); $this->page->markdown = ''; } @@ -52,7 +59,14 @@ public function setNewMarkdown(string $markdown, User $updater): void $markdown = $this->extractBase64ImagesFromMarkdown($markdown, $updater); $this->page->markdown = $markdown; $html = (new MarkdownToHtml($markdown))->convert(); - $this->page->html = $this->formatHtml($html); + $html = $this->formatHtml($html); + + $themeResult = Theme::dispatch(ThemeEvents::PAGE_CONTENT_PRE_STORE, $html, $this->page); + if (is_string($themeResult)) { + $html = $themeResult; + } + + $this->page->html = $html; $this->page->text = $this->toPlainText(); } @@ -81,7 +95,7 @@ protected function extractBase64ImagesFromHtml(string $htmlText, User $updater): /** * Convert all inline base64 content to uploaded image files. - * Regex is used to locate the start of data-uri definitions then + * Regex is used to locate the start of data-uri definitions, then * manual looping over content is done to parse the whole data uri. * Attempting to capture the whole data uri using regex can cause PHP * PCRE limits to be hit with larger, multi-MB, files. @@ -301,7 +315,7 @@ public function render(bool $blankIncludes = false): string $html = $this->page->html ?? ''; if (empty($html)) { - return $html; + return $this->handlePostRender(''); } $doc = new HtmlDocument($html); @@ -322,7 +336,7 @@ public function render(bool $blankIncludes = false): string $cacheKey = $this->getContentCacheKey($doc->getBodyInnerHtml()); $cached = cache()->get($cacheKey, null); if ($cached !== null) { - return $cached; + return $this->handlePostRender($cached); } $filterConfig = HtmlContentFilterConfig::fromConfigString(config('app.content_filtering')); @@ -332,7 +346,13 @@ public function render(bool $blankIncludes = false): string $cacheTime = 86400 * 7; // 1 week cache()->put($cacheKey, $filtered, $cacheTime); - return $filtered; + return $this->handlePostRender($filtered); + } + + protected function handlePostRender(string $html): string + { + $themeResult = Theme::dispatch(ThemeEvents::PAGE_CONTENT_POST_RENDER, $html, $this->page); + return is_string($themeResult) ? $themeResult : $html; } protected function getContentCacheKey(string $html): string diff --git a/app/Theming/ThemeEvents.php b/app/Theming/ThemeEvents.php index 71778ec4484..511a9c1de7a 100644 --- a/app/Theming/ThemeEvents.php +++ b/app/Theming/ThemeEvents.php @@ -111,6 +111,31 @@ class ThemeEvents */ const OIDC_ID_TOKEN_PRE_VALIDATE = 'oidc_id_token_pre_validate'; + /** + * Page content post-render event. + * Runs after any display rendering of page content, typically when page content is being processed for viewing. + * Rendering typically includes parsing of page includes, and content filtering. + * Provides the HTML content about to be shown, along with the related page instance. + * If the listener returns a string value, that will be used as the HTML content instead. + * + * @param string $html + * @param \BookStack\Entities\Models\Page $page + * @return string|null + */ + const PAGE_CONTENT_POST_RENDER = 'page_content_post_render'; + + /** + * Page content pre-store event. + * Runs just before page HTML is stored in the database, after BookStack's own processing. + * Provides the HTML content about to be stored, along with the related page instance. + * If the listener returns a string value, that will be used as the HTML content instead. + * + * @param string $html + * @param \BookStack\Entities\Models\Page $page + * @return string|null + */ + const PAGE_CONTENT_PRE_STORE = 'page_content_pre_store'; + /** * Page include parse event. * Runs when a page include tag is being parsed, typically when page content is being processed for viewing. diff --git a/tests/Theme/LogicalThemeEventsTest.php b/tests/Theme/LogicalThemeEventsTest.php index 0a4afd2f4e3..2add8386899 100644 --- a/tests/Theme/LogicalThemeEventsTest.php +++ b/tests/Theme/LogicalThemeEventsTest.php @@ -215,6 +215,82 @@ public function test_activity_logged() $this->assertEquals($book->id, $args[1]->id); } + public function test_page_content_pre_store_fires_on_page_save() + { + $page = $this->entities->page(); + + $args = []; + $callback = function (...$eventArgs) use (&$args) { + $args = $eventArgs; + return '

    New Content!

    '; + }; + + Theme::listen(ThemeEvents::PAGE_CONTENT_PRE_STORE, $callback); + + $this->asEditor(); + $this->entities->updatePage($page, ['name' => 'My cool update page!', 'html' => '

    Old content!

    ']); + + $this->assertCount(2, $args); + $this->assertEquals($page->id, $args[1]->id); + $this->assertEquals('

    Old content!

    ', $args[0]); + + $newPageHtml = $page->refresh()->html; + $this->assertEquals('

    New Content!

    ', $newPageHtml); + } + + public function test_page_content_pre_store_does_not_change_content_if_nothing_returned() + { + $page = $this->entities->page(); + Theme::listen(ThemeEvents::PAGE_CONTENT_PRE_STORE, fn() => null); + + $this->asEditor(); + $this->entities->updatePage($page, ['name' => 'My cool update page!', 'html' => '

    Old content!

    ']); + + $newPageHtml = $page->refresh()->html; + $this->assertEquals('

    Old content!

    ', $newPageHtml); + } + + public function test_page_content_post_render_fires_on_page_view() + { + $page = $this->entities->page(); + $page->html = '

    Old content!

    '; + $page->save(); + + $args = []; + $callback = function (...$eventArgs) use (&$args) { + $args = $eventArgs; + return '

    New postrendercontentforyou!

    '; + }; + + Theme::listen(ThemeEvents::PAGE_CONTENT_POST_RENDER, $callback); + + $resp = $this->asEditor()->get($page->getUrl()); + $resp->assertSee('

    New postrendercontentforyou!

    ', false); + + $this->assertCount(2, $args); + $this->assertEquals($page->id, $args[1]->id); + $this->assertEquals('

    Old content!

    ', $args[0]); + } + + public function test_page_content_post_render_returns_original_content_if_no_return() + { + $page = $this->entities->page(); + $page->html = '

    Old content!

    '; + $page->save(); + + $args = []; + $callback = function (...$eventArgs) use (&$args) { + $args = $eventArgs; + }; + + Theme::listen(ThemeEvents::PAGE_CONTENT_POST_RENDER, $callback); + + $resp = $this->asEditor()->get($page->getUrl()); + $resp->assertSee('

    Old content!

    ', false); + + $this->assertCount(2, $args); + } + public function test_page_include_parse() { /** @var Page $page */ From 151823b84e227e6ed63a216ff970ce2fa5491fca Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 8 Mar 2026 10:26:00 +0000 Subject: [PATCH 073/204] Theme Modules: Added easier way to insert HTML head content --- app/Theming/CustomHtmlHeadContentProvider.php | 25 +++++++++++++++++-- app/Theming/ThemeService.php | 14 +++++++++++ dev/docs/theme-system-modules.md | 1 + .../views/layouts/parts/custom-head.blade.php | 2 +- tests/Theme/ThemeModuleTest.php | 18 +++++++++++++ 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/app/Theming/CustomHtmlHeadContentProvider.php b/app/Theming/CustomHtmlHeadContentProvider.php index 9f794a077ba..209070997a3 100644 --- a/app/Theming/CustomHtmlHeadContentProvider.php +++ b/app/Theming/CustomHtmlHeadContentProvider.php @@ -12,7 +12,8 @@ class CustomHtmlHeadContentProvider { public function __construct( protected CspService $cspService, - protected Cache $cache + protected Cache $cache, + protected ThemeService $themeService, ) { } @@ -23,8 +24,9 @@ public function __construct( public function forWeb(): string { $content = $this->getSourceContent(); - $hash = md5($content); + $hash = md5($content) . ':' . $this->themeService->getModulesHash(); $html = $this->cache->remember('custom-head-web:' . $hash, 86400, function () use ($content) { + $content .= "\n" . $this->getModuleHeadContent(); return HtmlNonceApplicator::prepare($content); }); @@ -53,4 +55,23 @@ protected function getSourceContent(): string { return setting('app-custom-head', ''); } + + /** + * Get any custom head content from installed modules. + */ + protected function getModuleHeadContent(): string + { + $content = ''; + foreach ($this->themeService->getModules() as $module) { + $headContentPath = $module->path('head'); + if (file_exists($headContentPath) && is_dir($headContentPath)) { + $htmlFiles = glob($headContentPath . '/*.html'); + foreach ($htmlFiles as $file) { + $content .= file_get_contents($file); + } + } + } + + return $content; + } } diff --git a/app/Theming/ThemeService.php b/app/Theming/ThemeService.php index 6013bb5586d..864061c1ca6 100644 --- a/app/Theming/ThemeService.php +++ b/app/Theming/ThemeService.php @@ -126,6 +126,20 @@ public function getModules(): array return $this->modules; } + /** + * Get a hash to represent the currently loaded modules. + */ + public function getModulesHash(): string + { + $key = ""; + + foreach ($this->modules as $module) { + $key .= $module->name . ':' . $module->version . ';'; + } + + return md5($key); + } + /** * Look for a specific file within the theme or its modules. * Returns the first file found or null if not found. diff --git a/dev/docs/theme-system-modules.md b/dev/docs/theme-system-modules.md index 10eec2275d0..8aa9370ed26 100644 --- a/dev/docs/theme-system-modules.md +++ b/dev/docs/theme-system-modules.md @@ -24,6 +24,7 @@ The content within the module folder should then follow this format: - `bookstack-module.json` - REQUIRED - A JSON file containing [the metadata](#module-json-metadata) for the module. - `functions.php` - OPTIONAL - A PHP file containing code for the [logical theme system](logical-theme-system.md). +- `head/` - OPTIONAL - A folder containing HTML files which will be included into the HTML head of app-views. - `icons/` - OPTIONAL - A folder containing any icons to use as per [the visual theme system](visual-theme-system.md#customizing-icons). - `lang/` - OPTIONAL - A folder containing any language files to use as per [the visual theme system](visual-theme-system.md#customizing-text-content). - `public/` - OPTIONAL - A folder containing any files to expose into public web-space as per [the visual theme system](visual-theme-system.md#publicly-accessible-files). diff --git a/resources/views/layouts/parts/custom-head.blade.php b/resources/views/layouts/parts/custom-head.blade.php index a13215cf813..caa177030fa 100644 --- a/resources/views/layouts/parts/custom-head.blade.php +++ b/resources/views/layouts/parts/custom-head.blade.php @@ -1,6 +1,6 @@ @inject('headContent', 'BookStack\Theming\CustomHtmlHeadContentProvider') -@if(setting('app-custom-head') && !request()->routeIs('settings.category')) +@if(!request()->routeIs('settings.category')) {!! $headContent->forWeb() !!} diff --git a/tests/Theme/ThemeModuleTest.php b/tests/Theme/ThemeModuleTest.php index b2f912dd737..d1c7225b6af 100644 --- a/tests/Theme/ThemeModuleTest.php +++ b/tests/Theme/ThemeModuleTest.php @@ -3,6 +3,7 @@ namespace Tests\Theme; use BookStack\Facades\Theme; +use BookStack\Util\CspService; use Tests\TestCase; class ThemeModuleTest extends TestCase @@ -220,6 +221,23 @@ public function test_module_can_use_theme_view_render_functions() }); } + public function test_module_can_provide_head_content() + { + $this->usingModuleFolder(function (string $moduleFolderPath) { + mkdir($moduleFolderPath . '/head', 0777, true); + file_put_contents($moduleFolderPath . '/head/hello.html', ''); + + $this->refreshApplication(); + + $cspService = $this->app->make(CspService::class); + $nonce = $cspService->getNonce(); + + $resp = $this->asAdmin()->get('/'); + $resp->assertSee('', false); + $resp->assertSee('', false); + }); + } + protected function usingModuleFolder(callable $callback): void { $this->usingThemeFolder(function (string $themeFolder) use ($callback) { From 6d64262a61e4634285b4dedd3c49831976d1e06b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 10 Mar 2026 15:03:43 +0000 Subject: [PATCH 074/204] Revision Diffs: Added filtering post-diff render --- .../Controllers/PageRevisionController.php | 13 ++++++--- tests/Entity/PageRevisionTest.php | 29 +++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/app/Entities/Controllers/PageRevisionController.php b/app/Entities/Controllers/PageRevisionController.php index 35f1e8daf0e..4bc15e6e967 100644 --- a/app/Entities/Controllers/PageRevisionController.php +++ b/app/Entities/Controllers/PageRevisionController.php @@ -12,6 +12,8 @@ use BookStack\Facades\Activity; use BookStack\Http\Controller; use BookStack\Permissions\Permission; +use BookStack\Util\HtmlContentFilter; +use BookStack\Util\HtmlContentFilterConfig; use BookStack\Util\SimpleListOptions; use Illuminate\Http\Request; use Ssddanbrown\HtmlDiff\Diff; @@ -101,12 +103,15 @@ public function changes(string $bookSlug, string $pageSlug, int $revisionId) $prev = $revision->getPreviousRevision(); $prevContent = $prev->html ?? ''; - $diff = Diff::excecute($prevContent, $revision->html); + + // TODO - Refactor PageContent so we can de-dupe these steps + $rawDiff = Diff::excecute($prevContent, $revision->html); + $filterConfig = HtmlContentFilterConfig::fromConfigString(config('app.content_filtering')); + $filter = new HtmlContentFilter($filterConfig); + $diff = $filter->filterString($rawDiff); $page->fill($revision->toArray()); - // TODO - Refactor PageContent so we don't need to juggle this - $page->html = $revision->html; - $page->html = (new PageContent($page))->render(); + $page->html = ''; $this->setPageTitle(trans('entities.pages_revision_named', ['pageName' => $page->getShortName()])); return view('pages.revision', [ diff --git a/tests/Entity/PageRevisionTest.php b/tests/Entity/PageRevisionTest.php index 3828bd06e4a..d74c1f4881d 100644 --- a/tests/Entity/PageRevisionTest.php +++ b/tests/Entity/PageRevisionTest.php @@ -47,6 +47,20 @@ public function test_page_revision_preview_shows_content_of_revision() $revisionView->assertSee('new revision content'); } + public function test_page_revision_preview_filters_html_content() + { + $this->asEditor(); + $page = $this->entities->page(); + $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '

    expectthisthough

    ']); + $pageRevision = $page->revisions->last(); + $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '

    Updated content

    ']); + + $revisionView = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id); + $revisionView->assertStatus(200); + $revisionView->assertSee('expectthisthough'); + $revisionView->assertDontSee('dontwantthishere'); + } + public function test_page_revision_restore_updates_content() { $this->asEditor(); @@ -215,6 +229,21 @@ public function test_revision_changes_link_not_shown_for_oldest_revision() $html->assertElementContains('.item-list > .item-list-row:nth-child(2)', 'Changes'); } + public function test_page_changes_view_filters_html_content() + { + $this->asEditor(); + $page = $this->entities->page(); + $html = '

    expectthisthough

    '; + $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => $html]); + $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => $html]); + + $pageRevision = $page->revisions->last(); + $revisionView = $this->get("{$page->getUrl()}/revisions/{$pageRevision->id}/changes"); + $revisionView->assertStatus(200); + $revisionView->assertSee('expectthisthough'); + $revisionView->assertDontSee('dontwantthishere'); + } + public function test_revision_restore_action_only_visible_with_permission() { $page = $this->entities->page(); From 404e67afbc78583ba8bda7c95e3927a5b0cf5bb8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 10 Mar 2026 17:47:07 +0000 Subject: [PATCH 075/204] Page Revisions: Added testing coverage to basic diffing --- tests/Entity/PageRevisionTest.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/Entity/PageRevisionTest.php b/tests/Entity/PageRevisionTest.php index d74c1f4881d..132a10fa4da 100644 --- a/tests/Entity/PageRevisionTest.php +++ b/tests/Entity/PageRevisionTest.php @@ -229,7 +229,20 @@ public function test_revision_changes_link_not_shown_for_oldest_revision() $html->assertElementContains('.item-list > .item-list-row:nth-child(2)', 'Changes'); } - public function test_page_changes_view_filters_html_content() + public function test_revision_changes_view_shows_diff() + { + $this->asEditor(); + $page = $this->entities->page(); + $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '

    Hello there dog

    ']); + $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => '

    Hello there cat

    ']); + + $pageRevision = $page->revisions()->orderBy('id', 'desc')->first(); + $revisionView = $this->get("{$page->getUrl()}/revisions/{$pageRevision->id}/changes"); + $revisionView->assertStatus(200); + $revisionView->assertSee('

    Hello there dogcat

    ', false); + } + + public function test_revision_changes_view_filters_html_content() { $this->asEditor(); $page = $this->entities->page(); @@ -237,7 +250,7 @@ public function test_page_changes_view_filters_html_content() $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => $html]); $this->createRevisions($page, 1, ['name' => 'updated page', 'html' => $html]); - $pageRevision = $page->revisions->last(); + $pageRevision = $page->revisions()->orderBy('id', 'desc')->first(); $revisionView = $this->get("{$page->getUrl()}/revisions/{$pageRevision->id}/changes"); $revisionView->assertStatus(200); $revisionView->assertSee('expectthisthough'); From 6216c89f82ed66a7b2a4aae1fda67233a554ab20 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 10 Mar 2026 17:48:12 +0000 Subject: [PATCH 076/204] Packages: Updated PHP package versions --- composer.lock | 222 +++++++++++++++++++++++++------------------------- 1 file changed, 112 insertions(+), 110 deletions(-) diff --git a/composer.lock b/composer.lock index 18d0da62191..8ee6233b8ec 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.371.2", + "version": "3.372.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "32090a8ac3ec8859cb83bdde800b8f0ecf92d8ec" + "reference": "d1885c8c5db03c2a23121e6df58ef5693df41b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/32090a8ac3ec8859cb83bdde800b8f0ecf92d8ec", - "reference": "32090a8ac3ec8859cb83bdde800b8f0ecf92d8ec", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d1885c8c5db03c2a23121e6df58ef5693df41b95", + "reference": "d1885c8c5db03c2a23121e6df58ef5693df41b95", "shasum": "" }, "require": { @@ -135,11 +135,11 @@ "authors": [ { "name": "Amazon Web Services", - "homepage": "http://aws.amazon.com" + "homepage": "https://aws.amazon.com" } ], "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", - "homepage": "http://aws.amazon.com/sdkforphp", + "homepage": "https://aws.amazon.com/sdk-for-php", "keywords": [ "amazon", "aws", @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.371.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.372.2" }, - "time": "2026-02-26T19:06:10+00:00" + "time": "2026-03-09T18:21:50+00:00" }, { "name": "bacon/bacon-qr-code", @@ -635,16 +635,16 @@ }, { "name": "dompdf/dompdf", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "db712c90c5b9868df3600e64e68da62e78a34623" + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/db712c90c5b9868df3600e64e68da62e78a34623", - "reference": "db712c90c5b9868df3600e64e68da62e78a34623", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", "shasum": "" }, "require": { @@ -693,9 +693,9 @@ "homepage": "https://github.com/dompdf/dompdf", "support": { "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/v3.1.4" + "source": "https://github.com/dompdf/dompdf/tree/v3.1.5" }, - "time": "2025-10-29T12:43:30+00:00" + "time": "2026-03-03T13:54:37+00:00" }, { "name": "dompdf/php-font-lib", @@ -1387,16 +1387,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.8.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "21dc724a0583619cd1652f673303492272778051" + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", - "reference": "21dc724a0583619cd1652f673303492272778051", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", "shasum": "" }, "require": { @@ -1412,6 +1412,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", + "jshttp/mime-db": "1.54.0.1", "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { @@ -1483,7 +1484,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.8.0" + "source": "https://github.com/guzzle/psr7/tree/2.9.0" }, "funding": [ { @@ -1499,7 +1500,7 @@ "type": "tidelift" } ], - "time": "2025-08-23T21:21:41+00:00" + "time": "2026-03-10T16:41:02+00:00" }, { "name": "guzzlehttp/uri-template", @@ -1800,16 +1801,16 @@ }, { "name": "laravel/framework", - "version": "v12.53.0", + "version": "v12.54.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "f57f035c0d34503d9ff30be76159bb35a003cd1f" + "reference": "e908e117421bcade301b174aba2d3e8cc1e1f213" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/f57f035c0d34503d9ff30be76159bb35a003cd1f", - "reference": "f57f035c0d34503d9ff30be76159bb35a003cd1f", + "url": "https://api.github.com/repos/laravel/framework/zipball/e908e117421bcade301b174aba2d3e8cc1e1f213", + "reference": "e908e117421bcade301b174aba2d3e8cc1e1f213", "shasum": "" }, "require": { @@ -1830,7 +1831,7 @@ "guzzlehttp/uri-template": "^1.0", "laravel/prompts": "^0.3.0", "laravel/serializable-closure": "^1.3|^2.0", - "league/commonmark": "^2.7", + "league/commonmark": "^2.8.1", "league/flysystem": "^3.25.1", "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", @@ -2018,20 +2019,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-02-24T14:35:15+00:00" + "time": "2026-03-10T15:30:40+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.13", + "version": "v0.3.14", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "ed8c466571b37e977532fb2fd3c272c784d7050d" + "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/ed8c466571b37e977532fb2fd3c272c784d7050d", - "reference": "ed8c466571b37e977532fb2fd3c272c784d7050d", + "url": "https://api.github.com/repos/laravel/prompts/zipball/9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", + "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", "shasum": "" }, "require": { @@ -2075,9 +2076,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.13" + "source": "https://github.com/laravel/prompts/tree/v0.3.14" }, - "time": "2026-02-06T12:17:10+00:00" + "time": "2026-03-01T09:02:38+00:00" }, { "name": "laravel/serializable-closure", @@ -2142,16 +2143,16 @@ }, { "name": "laravel/socialite", - "version": "v5.24.3", + "version": "v5.25.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "0feb62267e7b8abc68593ca37639ad302728c129" + "reference": "231f572e1a37c9ca1fb8085e9fb8608285beafb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/0feb62267e7b8abc68593ca37639ad302728c129", - "reference": "0feb62267e7b8abc68593ca37639ad302728c129", + "url": "https://api.github.com/repos/laravel/socialite/zipball/231f572e1a37c9ca1fb8085e9fb8608285beafb3", + "reference": "231f572e1a37c9ca1fb8085e9fb8608285beafb3", "shasum": "" }, "require": { @@ -2210,7 +2211,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2026-02-21T13:32:50+00:00" + "time": "2026-02-27T13:56:35+00:00" }, { "name": "laravel/tinker", @@ -2280,16 +2281,16 @@ }, { "name": "league/commonmark", - "version": "2.8.0", + "version": "2.8.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb" + "reference": "84b1ca48347efdbe775426f108622a42735a6579" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb", - "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/84b1ca48347efdbe775426f108622a42735a6579", + "reference": "84b1ca48347efdbe775426f108622a42735a6579", "shasum": "" }, "require": { @@ -2314,9 +2315,9 @@ "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0 | ^7.0", - "symfony/process": "^5.4 | ^6.0 | ^7.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", + "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" }, @@ -2383,7 +2384,7 @@ "type": "tidelift" } ], - "time": "2025-11-26T21:48:24+00:00" + "time": "2026-03-05T21:37:03+00:00" }, { "name": "league/config", @@ -4188,16 +4189,16 @@ }, { "name": "predis/predis", - "version": "v3.4.1", + "version": "v3.4.2", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "0850f2f36ee179f0ff96c92c750e1366c6cd754c" + "reference": "2033429520d8997a7815a2485f56abe6d2d0e075" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/0850f2f36ee179f0ff96c92c750e1366c6cd754c", - "reference": "0850f2f36ee179f0ff96c92c750e1366c6cd754c", + "url": "https://api.github.com/repos/predis/predis/zipball/2033429520d8997a7815a2485f56abe6d2d0e075", + "reference": "2033429520d8997a7815a2485f56abe6d2d0e075", "shasum": "" }, "require": { @@ -4239,7 +4240,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v3.4.1" + "source": "https://github.com/predis/predis/tree/v3.4.2" }, "funding": [ { @@ -4247,7 +4248,7 @@ "type": "github" } ], - "time": "2026-02-23T19:51:21+00:00" + "time": "2026-03-09T20:33:04+00:00" }, { "name": "psr/clock", @@ -4663,16 +4664,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.20", + "version": "v0.12.21", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373" + "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/19678eb6b952a03b8a1d96ecee9edba518bb0373", - "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", + "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", "shasum": "" }, "require": { @@ -4736,9 +4737,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.20" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.21" }, - "time": "2026-02-11T15:05:28+00:00" + "time": "2026-03-06T21:21:28+00:00" }, { "name": "ralouphie/getallheaders", @@ -4982,16 +4983,16 @@ }, { "name": "sabberworm/php-css-parser", - "version": "v9.2.0", + "version": "v9.3.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "59373045e11ad47b5c18fc615feee0219e42f6d3" + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/59373045e11ad47b5c18fc615feee0219e42f6d3", - "reference": "59373045e11ad47b5c18fc615feee0219e42f6d3", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", "shasum": "" }, "require": { @@ -5018,7 +5019,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.3.x-dev" + "dev-main": "9.4.x-dev" } }, "autoload": { @@ -5056,9 +5057,9 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.2.0" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0" }, - "time": "2026-02-21T17:12:03+00:00" + "time": "2026-03-03T17:31:43+00:00" }, { "name": "socialiteproviders/discord", @@ -5508,16 +5509,16 @@ }, { "name": "symfony/console", - "version": "v7.4.6", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "6d643a93b47398599124022eb24d97c153c12f27" + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/6d643a93b47398599124022eb24d97c153c12f27", - "reference": "6d643a93b47398599124022eb24d97c153c12f27", + "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", "shasum": "" }, "require": { @@ -5582,7 +5583,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.6" + "source": "https://github.com/symfony/console/tree/v7.4.7" }, "funding": [ { @@ -5602,7 +5603,7 @@ "type": "tidelift" } ], - "time": "2026-02-25T17:02:47+00:00" + "time": "2026-03-06T14:06:20+00:00" }, { "name": "symfony/css-selector", @@ -6123,16 +6124,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.4.6", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "fd97d5e926e988a363cef56fbbf88c5c528e9065" + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/fd97d5e926e988a363cef56fbbf88c5c528e9065", - "reference": "fd97d5e926e988a363cef56fbbf88c5c528e9065", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81", "shasum": "" }, "require": { @@ -6181,7 +6182,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.6" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.7" }, "funding": [ { @@ -6201,20 +6202,20 @@ "type": "tidelift" } ], - "time": "2026-02-21T16:25:55+00:00" + "time": "2026-03-06T13:15:18+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.6", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "002ac0cf4cd972a7fd0912dcd513a95e8a81ce83" + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/002ac0cf4cd972a7fd0912dcd513a95e8a81ce83", - "reference": "002ac0cf4cd972a7fd0912dcd513a95e8a81ce83", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3b3fcf386c809be990c922e10e4c620d6367cab1", + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1", "shasum": "" }, "require": { @@ -6300,7 +6301,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.6" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.7" }, "funding": [ { @@ -6320,7 +6321,7 @@ "type": "tidelift" } ], - "time": "2026-02-26T08:30:57+00:00" + "time": "2026-03-06T16:33:18+00:00" }, { "name": "symfony/mailer", @@ -6408,16 +6409,16 @@ }, { "name": "symfony/mime", - "version": "v7.4.6", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "9fc881d95feae4c6c48678cb6372bd8a7ba04f5f" + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/9fc881d95feae4c6c48678cb6372bd8a7ba04f5f", - "reference": "9fc881d95feae4c6c48678cb6372bd8a7ba04f5f", + "url": "https://api.github.com/repos/symfony/mime/zipball/da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1", "shasum": "" }, "require": { @@ -6473,7 +6474,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.6" + "source": "https://github.com/symfony/mime/tree/v7.4.7" }, "funding": [ { @@ -6493,7 +6494,7 @@ "type": "tidelift" } ], - "time": "2026-02-05T15:57:06+00:00" + "time": "2026-03-05T15:24:09+00:00" }, { "name": "symfony/polyfill-ctype", @@ -8721,40 +8722,40 @@ }, { "name": "larastan/larastan", - "version": "v3.9.2", + "version": "v3.9.3", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2" + "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", - "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", + "url": "https://api.github.com/repos/larastan/larastan/zipball/64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", + "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", "shasum": "" }, "require": { "ext-json": "*", "iamcal/sql-parser": "^0.7.0", - "illuminate/console": "^11.44.2 || ^12.4.1", - "illuminate/container": "^11.44.2 || ^12.4.1", - "illuminate/contracts": "^11.44.2 || ^12.4.1", - "illuminate/database": "^11.44.2 || ^12.4.1", - "illuminate/http": "^11.44.2 || ^12.4.1", - "illuminate/pipeline": "^11.44.2 || ^12.4.1", - "illuminate/support": "^11.44.2 || ^12.4.1", + "illuminate/console": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/container": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/contracts": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/database": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/http": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/pipeline": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/support": "^11.44.2 || ^12.4.1 || ^13", "php": "^8.2", "phpstan/phpstan": "^2.1.32" }, "require-dev": { "doctrine/coding-standard": "^13", - "laravel/framework": "^11.44.2 || ^12.7.2", + "laravel/framework": "^11.44.2 || ^12.7.2 || ^13", "mockery/mockery": "^1.6.12", "nikic/php-parser": "^5.4", - "orchestra/canvas": "^v9.2.2 || ^10.0.1", - "orchestra/testbench-core": "^9.12.0 || ^10.1", + "orchestra/canvas": "^v9.2.2 || ^10.0.1 || ^11", + "orchestra/testbench-core": "^9.12.0 || ^10.1 || ^11", "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpunit/phpunit": "^10.5.35 || ^11.5.15" + "phpunit/phpunit": "^10.5.35 || ^11.5.15 || ^12.5.8" }, "suggest": { "orchestra/testbench": "Using Larastan for analysing a package needs Testbench", @@ -8799,7 +8800,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.9.2" + "source": "https://github.com/larastan/larastan/tree/v3.9.3" }, "funding": [ { @@ -8807,7 +8808,7 @@ "type": "github" } ], - "time": "2026-01-30T15:16:32+00:00" + "time": "2026-02-20T12:07:12+00:00" }, { "name": "mockery/mockery", @@ -10743,21 +10744,22 @@ }, { "name": "ssddanbrown/asserthtml", - "version": "v3.1.0", + "version": "v3.2.0", "source": { "type": "git", "url": "https://codeberg.org/danb/asserthtml", - "reference": "cf8206171d667d43e1bdde17d67191f30e95c8a0" + "reference": "0811b5c8d541f344c193bd7f2c2d79d13d23b141" }, "dist": { "type": "zip", - "url": "https://codeberg.org/api/v1/repos/danb/asserthtml/archive/%prettyVersion%.zip" + "url": "https://codeberg.org/api/v1/repos/danb/asserthtml/archive/%prettyVersion%.zip", + "reference": "0811b5c8d541f344c193bd7f2c2d79d13d23b141" }, "require": { "ext-dom": "*", "ext-json": "*", "php": ">=8.1", - "phpunit/phpunit": "^10.0|^11.0", + "phpunit/phpunit": "^10.0|^11.0|^12.0|^13.0", "symfony/css-selector": "^6.0|^7.0", "symfony/dom-crawler": "^6.0|^7.0" }, @@ -10783,7 +10785,7 @@ ], "description": "HTML Content Assertions for PHPUnit", "homepage": "https://codeberg.org/danb/asserthtml", - "time": "2025-01-11T13:35:55+00:00" + "time": "2026-03-04T14:19:44+00:00" }, { "name": "staabm/side-effects-detector", From 6e7cc169d1527c18556d536159e0e23df85c381e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 10 Mar 2026 18:31:51 +0000 Subject: [PATCH 077/204] Preferences: Updated return redirect with better origin checks As suggested by Alex Dan in their security report. --- app/Http/Controller.php | 16 ++++++++++++++-- tests/User/UserPreferencesTest.php | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/app/Http/Controller.php b/app/Http/Controller.php index 5d3be4951ca..1a0f5932e6f 100644 --- a/app/Http/Controller.php +++ b/app/Http/Controller.php @@ -167,14 +167,26 @@ protected function getImageValidationRules(): array /** * Redirect to the URL provided in the request as a '_return' parameter. - * Will check that the parameter leads to a URL under the root path of the system. + * Will check that the parameter leads to a URL under the same origin as the application. */ protected function redirectToRequest(Request $request): RedirectResponse { $basePath = url('/'); $returnUrl = $request->input('_return') ?? $basePath; - if (!str_starts_with($returnUrl, $basePath)) { + // Only allow use of _return on requests where we expect CSRF to be active + // to prevent it potentially being used as an open redirect + $allowedMethods = ['POST', 'PUT', 'PATCH', 'DELETE']; + if (!in_array($request->getMethod(), $allowedMethods)) { + return redirect($basePath); + } + + $intendedUrl = parse_url($returnUrl); + $baseUrl = parse_url($basePath); + $isSameOrigin = ($intendedUrl['host'] ?? '') === ($baseUrl['host'] ?? '') + && ($intendedUrl['scheme'] ?? '') === ($baseUrl['scheme'] ?? '') + && ($intendedUrl['port'] ?? 0) === ($baseUrl['port'] ?? 0); + if (!$isSameOrigin) { return redirect($basePath); } diff --git a/tests/User/UserPreferencesTest.php b/tests/User/UserPreferencesTest.php index ff3cb63ca70..e893f002dfd 100644 --- a/tests/User/UserPreferencesTest.php +++ b/tests/User/UserPreferencesTest.php @@ -153,6 +153,26 @@ public function test_shelf_view_type_change() ->assertElementNotExists('.content-wrap .entity-list-item'); } + public function test_redirect_on_preference_change_checks_host() + { + $expectedByRedirect = [ + 'http://localhost/beans' => 'http://localhost/beans', + 'https://localhost/beans' => 'http://localhost', + 'http://localhost:9090/beans' => 'http://localhost', + 'http://localhost.example.com/beans' => 'http://localhost', + 'http://localhost@example.com/beans' => 'http://localhost', + ]; + + $this->asEditor(); + foreach ($expectedByRedirect as $url => $expected) { + $req = $this->patch("/preferences/change-view/bookshelf", [ + 'view' => 'grid', + '_return' => $url, + ]); + $req->assertRedirect($expected); + } + } + public function test_update_code_language_favourite() { $editor = $this->users->editor(); From e3fcd26f12ccda464cb22bc4e95bad108878bca0 Mon Sep 17 00:00:00 2001 From: Claudio Valdez Date: Wed, 11 Mar 2026 12:30:59 -0300 Subject: [PATCH 078/204] Add mfa reset button for admin s on user profile edit --- app/Users/Controllers/UserController.php | 13 +++++++++++++ lang/en/settings.php | 5 +++++ resources/views/users/edit.blade.php | 20 ++++++++++++++++++++ routes/web.php | 1 + 4 files changed, 39 insertions(+) diff --git a/app/Users/Controllers/UserController.php b/app/Users/Controllers/UserController.php index 494221b143e..6d1a47c0de7 100644 --- a/app/Users/Controllers/UserController.php +++ b/app/Users/Controllers/UserController.php @@ -208,4 +208,17 @@ public function destroy(Request $request, int $id) return redirect('/settings/users'); } + + /** + * Reset MFA for the specified user. + */ + public function resetMfa(Request $request, int $id) + { + $this->checkPermission(Permission::UsersManage); + $user = $this->userRepo->getById($id); + // Resetear el 2FA del usuario + $user->mfaValues()->delete(); + session()->flash('success', trans('settings.users_mfa_reset_success', ['userName' => $user->name])); + return redirect()->back(); + } } diff --git a/lang/en/settings.php b/lang/en/settings.php index c4d1eb136eb..8499aed6f90 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -263,6 +263,11 @@ '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 2FA', + 'users_mfa_reset_desc' => 'Reset and clear all configured MFA methods for :userName. They will be prompted to reconfigure on next login.', + 'users_mfa_reset_confirm' => 'Are you sure you want to reset 2FA for :userName?', + 'users_mfa_reset_success' => '2FA has been reset for :userName', + 'users_mfa_reset_error' => 'Failed to reset 2FA for :userName', // API Tokens 'user_api_token_create' => 'Create API Token', diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index 611653d6a80..64d45f50361 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -71,6 +71,26 @@ class="button outline">{{ trans('settings.users_mfa_configure') }} + @if(user()->hasSystemRole('admin')) +
    +
    +
    +
    + {{ trans('settings.users_mfa_reset') }} +

    {{ trans('settings.users_mfa_reset_desc', ['userName' => $user->name]) }}

    +
    +
    +
    id}/reset-mfa") }}" method="POST" style="display: inline;"> + @csrf + +
    +
    +
    +
    + @endif @if(count($activeSocialDrivers) > 0) diff --git a/routes/web.php b/routes/web.php index a20c0a3d3d0..2571da2f3a2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -251,6 +251,7 @@ Route::get('/settings/users/{id}', [UserControllers\UserController::class, 'edit']); Route::put('/settings/users/{id}', [UserControllers\UserController::class, 'update']); Route::delete('/settings/users/{id}', [UserControllers\UserController::class, 'destroy']); + Route::post('/settings/users/{id}/reset-mfa', [UserControllers\UserController::class, 'resetMfa']); // User Account Route::get('/my-account', [UserControllers\UserAccountController::class, 'redirect']); From 5f5fea7c83992cecca61b4aabfc1e07053dfa4c4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 12 Mar 2026 10:52:12 +0000 Subject: [PATCH 079/204] Deps: Bumped PHP packages before release --- composer.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/composer.lock b/composer.lock index 8ee6233b8ec..d8ea0066265 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.372.2", + "version": "3.373.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "d1885c8c5db03c2a23121e6df58ef5693df41b95" + "reference": "fb74a2dca7ae2363e929c5cea33a4a4db0d22690" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d1885c8c5db03c2a23121e6df58ef5693df41b95", - "reference": "d1885c8c5db03c2a23121e6df58ef5693df41b95", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/fb74a2dca7ae2363e929c5cea33a4a4db0d22690", + "reference": "fb74a2dca7ae2363e929c5cea33a4a4db0d22690", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.372.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.373.0" }, - "time": "2026-03-09T18:21:50+00:00" + "time": "2026-03-11T18:33:36+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1801,16 +1801,16 @@ }, { "name": "laravel/framework", - "version": "v12.54.0", + "version": "v12.54.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "e908e117421bcade301b174aba2d3e8cc1e1f213" + "reference": "325497463e7599cd14224c422c6e5dd2fe832868" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/e908e117421bcade301b174aba2d3e8cc1e1f213", - "reference": "e908e117421bcade301b174aba2d3e8cc1e1f213", + "url": "https://api.github.com/repos/laravel/framework/zipball/325497463e7599cd14224c422c6e5dd2fe832868", + "reference": "325497463e7599cd14224c422c6e5dd2fe832868", "shasum": "" }, "require": { @@ -2019,7 +2019,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-03-10T15:30:40+00:00" + "time": "2026-03-10T20:25:56+00:00" }, { "name": "laravel/prompts", @@ -3361,16 +3361,16 @@ }, { "name": "nesbot/carbon", - "version": "3.11.1", + "version": "3.11.3", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "f438fcc98f92babee98381d399c65336f3a3827f" + "reference": "6a7e652845bb018c668220c2a545aded8594fbbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f", - "reference": "f438fcc98f92babee98381d399c65336f3a3827f", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6a7e652845bb018c668220c2a545aded8594fbbf", + "reference": "6a7e652845bb018c668220c2a545aded8594fbbf", "shasum": "" }, "require": { @@ -3462,7 +3462,7 @@ "type": "tidelift" } ], - "time": "2026-01-29T09:26:29+00:00" + "time": "2026-03-11T17:23:39+00:00" }, { "name": "nette/schema", From 60a3b0c0acb9685fb303b25f503b690cb2083213 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 12 Mar 2026 17:04:36 +0000 Subject: [PATCH 080/204] API examples: Updated books-read to include shelf info --- dev/api/responses/books-read.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/dev/api/responses/books-read.json b/dev/api/responses/books-read.json index 582744f99a5..0c8ff473df5 100644 --- a/dev/api/responses/books-read.json +++ b/dev/api/responses/books-read.json @@ -79,5 +79,17 @@ "path": "/uploads/images/cover_book/2020-01/sjovall_m117hUWMu40.jpg", "type": "cover_book", "uploaded_to": 16 - } + }, + "shelves": [ + { + "id": 1, + "name": "Great reads", + "slug": "great-reads" + }, + { + "id": 5, + "name": "Personal Books", + "slug": "personal-books" + } + ] } \ No newline at end of file From f4c9d2b0492d5a2dc3de052352f7a2e90d150f3f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 13 Mar 2026 13:35:28 +0000 Subject: [PATCH 081/204] Exports: Fixed scope of pages in chapter MD export Added tests to cover children of all MD exports --- app/Exports/ExportFormatter.php | 2 +- tests/Exports/MarkdownExportTest.php | 46 ++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/app/Exports/ExportFormatter.php b/app/Exports/ExportFormatter.php index ad489aba1cb..c5973eace29 100644 --- a/app/Exports/ExportFormatter.php +++ b/app/Exports/ExportFormatter.php @@ -323,7 +323,7 @@ public function chapterToMarkdown(Chapter $chapter): string $text .= $description . "\n\n"; } - foreach ($chapter->pages as $page) { + foreach ($chapter->getVisiblePages() as $page) { $text .= $this->pageToMarkdown($page) . "\n\n"; } diff --git a/tests/Exports/MarkdownExportTest.php b/tests/Exports/MarkdownExportTest.php index 6bf585d5903..09928ced29e 100644 --- a/tests/Exports/MarkdownExportTest.php +++ b/tests/Exports/MarkdownExportTest.php @@ -56,6 +56,20 @@ public function test_chapter_markdown_export() $resp->assertSee('My **chapter** description'); } + public function test_chapter_markdown_export_pages_are_permission_controlled() + { + $chapter = $this->entities->chapterHasPages(); + $page = $chapter->pages()->first(); + $page->name = 'MyPageWhichShouldNotBeFound'; + $page->save(); + $this->permissions->disableEntityInheritedPermissions($page); + + $resp = $this->asEditor()->get($chapter->getUrl('/export/markdown')); + + $resp->assertSee('# ' . $chapter->name); + $resp->assertDontSee('MyPageWhichShouldNotBeFound'); + } + public function test_book_markdown_export() { $book = Book::query()->whereHas('pages')->whereHas('chapters')->first(); @@ -76,6 +90,38 @@ public function test_book_markdown_export() $resp->assertSee('My **chapter** description'); } + public function test_book_markdown_export_chapters_are_permission_controlled() + { + $book = $this->entities->bookHasChaptersAndPages(); + $chapter = $book->chapters()->first(); + $page = $chapter->pages()->first(); + $page->name = 'MyPageWhichShouldNotBeFound'; + $page->save(); + $chapter->name = 'MyChapterWhichShouldNotBeFound'; + $chapter->save(); + $this->permissions->disableEntityInheritedPermissions($chapter); + + $resp = $this->asEditor()->get($book->getUrl('/export/markdown')); + + $resp->assertSee('# ' . $book->name); + $resp->assertDontSee('MyChapterWhichShouldNotBeFound'); + $resp->assertDontSee('MyPageWhichShouldNotBeFound'); + } + + public function test_book_markdown_export_direct_pages_are_permission_controlled() + { + $book = $this->entities->bookHasChaptersAndPages(); + $page = $book->directPages()->first(); + $page->name = 'MyPageWhichShouldNotBeFound'; + $page->save(); + $this->permissions->disableEntityInheritedPermissions($page); + + $resp = $this->asEditor()->get($book->getUrl('/export/markdown')); + + $resp->assertSee('# ' . $book->name); + $resp->assertDontSee('MyPageWhichShouldNotBeFound'); + } + public function test_book_markdown_export_concats_immediate_pages_with_newlines() { /** @var Book $book */ From 7cbfd72920faf7e53c5b681119ac88adada811c3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Mar 2026 12:58:05 +0000 Subject: [PATCH 082/204] Merge pull request #6007 from BookStackApp/l10n_development Updated translations with latest Crowdin changes --- lang/ar/settings.php | 2 +- lang/bg/settings.php | 2 +- lang/bn/settings.php | 2 +- lang/bs/settings.php | 2 +- lang/ca/settings.php | 2 +- lang/cs/errors.php | 2 +- lang/cs/settings.php | 2 +- lang/cy/settings.php | 2 +- lang/da/settings.php | 2 +- lang/de/settings.php | 2 +- lang/de_informal/settings.php | 2 +- lang/el/settings.php | 2 +- lang/es/errors.php | 2 +- lang/es/settings.php | 2 +- lang/es_AR/errors.php | 2 +- lang/es_AR/settings.php | 2 +- lang/et/errors.php | 2 +- lang/et/settings.php | 2 +- lang/eu/settings.php | 2 +- lang/fa/settings.php | 2 +- lang/fi/settings.php | 2 +- lang/fr/errors.php | 4 ++-- lang/fr/settings.php | 2 +- lang/he/settings.php | 2 +- lang/hr/settings.php | 2 +- lang/hu/settings.php | 2 +- lang/id/settings.php | 2 +- lang/is/settings.php | 2 +- lang/it/errors.php | 2 +- lang/it/settings.php | 2 +- lang/ja/errors.php | 2 +- lang/ja/settings.php | 2 +- lang/ka/settings.php | 2 +- lang/ko/settings.php | 2 +- lang/ku/settings.php | 2 +- lang/lt/settings.php | 2 +- lang/lv/settings.php | 2 +- lang/nb/settings.php | 2 +- lang/ne/settings.php | 2 +- lang/nl/settings.php | 2 +- lang/nn/settings.php | 2 +- lang/pl/settings.php | 2 +- lang/pt/settings.php | 2 +- lang/pt_BR/errors.php | 4 ++-- lang/pt_BR/notifications.php | 4 ++-- lang/pt_BR/preferences.php | 2 +- lang/pt_BR/settings.php | 6 +++--- lang/pt_BR/validation.php | 2 +- lang/ro/settings.php | 2 +- lang/ru/entities.php | 38 +++++++++++++++++------------------ lang/ru/notifications.php | 2 +- lang/ru/settings.php | 6 +++--- lang/sk/settings.php | 2 +- lang/sl/settings.php | 2 +- lang/sq/settings.php | 2 +- lang/sr/settings.php | 2 +- lang/sv/entities.php | 2 +- lang/sv/preferences.php | 2 +- lang/sv/settings.php | 2 +- lang/tk/settings.php | 2 +- lang/tr/settings.php | 2 +- lang/uk/settings.php | 2 +- lang/uz/settings.php | 2 +- lang/vi/settings.php | 2 +- lang/zh_CN/editor.php | 2 +- lang/zh_CN/entities.php | 10 ++++----- lang/zh_CN/errors.php | 4 ++-- lang/zh_CN/notifications.php | 4 ++-- lang/zh_CN/preferences.php | 2 +- lang/zh_CN/settings.php | 12 +++++------ lang/zh_CN/validation.php | 2 +- lang/zh_TW/settings.php | 2 +- 72 files changed, 108 insertions(+), 108 deletions(-) diff --git a/lang/ar/settings.php b/lang/ar/settings.php index dc95ac8468f..3191bbe3a0a 100644 --- a/lang/ar/settings.php +++ b/lang/ar/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'الفصول الأولى', 'sort_rule_op_chapters_last' => 'الفصول الأخيرة', 'sorting_page_limits' => 'حدود العرض لكل صفحة', - 'sorting_page_limits_desc' => 'تعيين عدد العناصر لإظهار كل صفحة في قوائم مختلفة داخل النظام. عادةً ما يكون الرقم الأقل هو الأكثر أداء، بينما وضع رقم أعلى يغني عن النقر على صفحات متعددة. يوصى باستخدام مضاعفات رقم ٣ (18 و 24 و 30 و إلخ...).', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'الصيانة', diff --git a/lang/bg/settings.php b/lang/bg/settings.php index ae770c559cf..a1297e44613 100644 --- a/lang/bg/settings.php +++ b/lang/bg/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Поддръжка', diff --git a/lang/bn/settings.php b/lang/bn/settings.php index 6d0f4ab88b2..94ad059d4ce 100644 --- a/lang/bn/settings.php +++ b/lang/bn/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Maintenance', diff --git a/lang/bs/settings.php b/lang/bs/settings.php index c68605fe1f8..c4d1eb136eb 100644 --- a/lang/bs/settings.php +++ b/lang/bs/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Maintenance', diff --git a/lang/ca/settings.php b/lang/ca/settings.php index 352291fe5b5..a890b9809d4 100644 --- a/lang/ca/settings.php +++ b/lang/ca/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Capítols a l\'inici', 'sort_rule_op_chapters_last' => 'Capítols al final', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Manteniment', diff --git a/lang/cs/errors.php b/lang/cs/errors.php index 17632064208..5d086456ba1 100644 --- a/lang/cs/errors.php +++ b/lang/cs/errors.php @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => 'Poskytnutý Token Secret neodpovídá použitému API tokenu', 'api_user_no_api_permission' => 'Vlastník použitého API tokenu nemá oprávnění provádět API volání', 'api_user_token_expired' => 'Platnost autorizačního tokenu vypršela', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Při používání API s ověřováním pomocí souborů cookie jsou povoleny pouze požadavky GET', // Settings & Maintenance 'maintenance_test_email_failure' => 'Při posílání testovacího e-mailu nastala chyba:', diff --git a/lang/cs/settings.php b/lang/cs/settings.php index 73ba6bfb077..ef25f1a2035 100644 --- a/lang/cs/settings.php +++ b/lang/cs/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Kapitoly jako první', 'sort_rule_op_chapters_last' => 'Kapitoly jako poslední', 'sorting_page_limits' => 'Počet zobrazených položek na stránce', - 'sorting_page_limits_desc' => 'Nastavte, kolik položek se má zobrazit na stránce v různých seznamech na webu. Obvykle bude nižší počet výkonnější, zatímco vyšší počet eliminuje nutnost proklikávat se několika stránkami. Doporučuje se použít sudý násobek čísla 3 (18, 24, 30 atd.).', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Údržba', diff --git a/lang/cy/settings.php b/lang/cy/settings.php index 29e86e28bb5..f4fbf0bba1b 100644 --- a/lang/cy/settings.php +++ b/lang/cy/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Cynnal', diff --git a/lang/da/settings.php b/lang/da/settings.php index 2f161ed4fe4..cd869c62fe0 100644 --- a/lang/da/settings.php +++ b/lang/da/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Kapitler først', 'sort_rule_op_chapters_last' => 'De sidste kapitler', 'sorting_page_limits' => 'Visningsgrænser pr. side', - 'sorting_page_limits_desc' => 'Angiv, hvor mange elementer der skal vises pr. side i forskellige lister i systemet. Typisk vil et lavere beløb være mere effektivt, mens et højere beløb undgår behovet for at klikke sig igennem flere sider. Det anbefales at bruge et lige multiplum af 3 (18, 24, 30 osv.).', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Vedligeholdelse', diff --git a/lang/de/settings.php b/lang/de/settings.php index a874089958a..e57ac52c457 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -105,7 +105,7 @@ 'sort_rule_op_chapters_first' => 'Kapitel zuerst', 'sort_rule_op_chapters_last' => 'Kapitel zuletzt', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Wartung', diff --git a/lang/de_informal/settings.php b/lang/de_informal/settings.php index ab9a075a0a7..fa175187b80 100644 --- a/lang/de_informal/settings.php +++ b/lang/de_informal/settings.php @@ -105,7 +105,7 @@ 'sort_rule_op_chapters_first' => 'Kapitel zuerst', 'sort_rule_op_chapters_last' => 'Kapitel zuletzt', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Wartung', diff --git a/lang/el/settings.php b/lang/el/settings.php index 67461604eb0..6ec5c4fddeb 100644 --- a/lang/el/settings.php +++ b/lang/el/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Συντήρηση', diff --git a/lang/es/errors.php b/lang/es/errors.php index 76581257e8c..25f82a5d872 100644 --- a/lang/es/errors.php +++ b/lang/es/errors.php @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => 'El secreto proporcionado para el token API usado es incorrecto', 'api_user_no_api_permission' => 'El propietario del token API usado no tiene permiso para hacer llamadas API', 'api_user_token_expired' => 'El token de autorización usado ha caducado', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Sólo se permiten peticiones GET cuando se utiliza el API con autenticación basada en cookies', // Settings & Maintenance 'maintenance_test_email_failure' => 'Error al enviar un email de prueba:', diff --git a/lang/es/settings.php b/lang/es/settings.php index 1a9927c8eca..bfd3ce1cfe4 100644 --- a/lang/es/settings.php +++ b/lang/es/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Capítulos al inicio', 'sort_rule_op_chapters_last' => 'Capítulos al final', 'sorting_page_limits' => 'Límites de visualización por página', - 'sorting_page_limits_desc' => 'Establecer cuántos elementos a mostrar por página en varias listas dentro del sistema. Normalmente una cantidad más baja rendirá mejor, mientras que una cantidad más alta evita la necesidad de hacer clic a través de varias páginas. Se recomienda utilizar un múltiplo par de 3 (18, 24, 30, etc).', + 'sorting_page_limits_desc' => 'Establecer cuántos elementos a mostrar por página en varias listas dentro del sistema. Normalmente una cantidad más baja rendirá mejor, mientras que una cantidad más alta evita la necesidad de hacer clic a través de varias páginas. Se recomienda utilizar un múltiplo de 6.', // Maintenance settings 'maint' => 'Mantenimiento', diff --git a/lang/es_AR/errors.php b/lang/es_AR/errors.php index 5e052c51d5a..6f6b25bcf98 100644 --- a/lang/es_AR/errors.php +++ b/lang/es_AR/errors.php @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => 'El secreto proporcionado para el token API usado es incorrecto', 'api_user_no_api_permission' => 'El propietario del token API usado no tiene permiso para hacer llamadas API', 'api_user_token_expired' => 'El token de autorización usado ha caducado', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Sólo se permiten peticiones GET cuando se utiliza el API con autenticación basada en cookies', // Settings & Maintenance 'maintenance_test_email_failure' => 'Error al enviar un email de prueba:', diff --git a/lang/es_AR/settings.php b/lang/es_AR/settings.php index 3eb41d2cc91..90f43a6f268 100644 --- a/lang/es_AR/settings.php +++ b/lang/es_AR/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Capítulos al inicio', 'sort_rule_op_chapters_last' => 'Capítulos al final', 'sorting_page_limits' => 'Límites de visualización por página', - 'sorting_page_limits_desc' => 'Establecer cuántos elementos a mostrar por página en varias listas dentro del sistema. Normalmente una cantidad más baja rendirá mejor, mientras que una cantidad más alta evita la necesidad de hacer clic a través de varias páginas. Se recomienda utilizar un múltiplo par de 3 (18, 24, 30, etc).', + 'sorting_page_limits_desc' => 'Establecer cuántos elementos a mostrar por página en varias listas dentro del sistema. Normalmente una cantidad más baja rendirá mejor, mientras que una cantidad más alta evita la necesidad de hacer clic a través de varias páginas. Se recomienda utilizar un múltiplo de 6.', // Maintenance settings 'maint' => 'Mantenimiento', diff --git a/lang/et/errors.php b/lang/et/errors.php index c9b9fbb4e8a..78a9d8c47fb 100644 --- a/lang/et/errors.php +++ b/lang/et/errors.php @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => 'API tunnusele lisatud salajane võti ei ole korrektne', 'api_user_no_api_permission' => 'Selle API tunnuse omanikul ei ole õigust API päringuid teha', 'api_user_token_expired' => 'Volitustunnus on aegunud', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Küpsistega autentimisel on API kasutamisel lubatud ainult GET päringud', // Settings & Maintenance 'maintenance_test_email_failure' => 'Test e-kirja saatmisel tekkis viga:', diff --git a/lang/et/settings.php b/lang/et/settings.php index fbd9d9c7e7c..bc5a7794e87 100644 --- a/lang/et/settings.php +++ b/lang/et/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Peatükid eespool', 'sort_rule_op_chapters_last' => 'Peatükid tagapool', 'sorting_page_limits' => 'Leheküljepõhised kuvalimiidid', - 'sorting_page_limits_desc' => 'Seadista, mitut objekti erinevates loendites ühel leheküljel kuvada. Väiksem väärtus tähendab reeglina paremat jõudlust, samas kui suurem väärtus vähendab vajadust mitut lehekülge läbi klikkida. Soovituslik on kasutada 3-ga jaguvat väärtust (18, 24, 30 jne).', + 'sorting_page_limits_desc' => 'Vali, mitu objekti erinevates nimekirjades ühel lehel kuvada. Madalam väärtus tähendab reeglina paremat jõudlust, samas kui kõrgem väärtus väldib vajadust mitmeid lehti läbi klikkida. Soovituslik on kasutada 6-ga jaguvat väärtust.', // Maintenance settings 'maint' => 'Hooldus', diff --git a/lang/eu/settings.php b/lang/eu/settings.php index 9a9227b8303..0f764dccbd2 100644 --- a/lang/eu/settings.php +++ b/lang/eu/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Mantentze-lanak', diff --git a/lang/fa/settings.php b/lang/fa/settings.php index abbfce470c3..2fa11511838 100644 --- a/lang/fa/settings.php +++ b/lang/fa/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'ابتدا فصل‌ها', 'sort_rule_op_chapters_last' => 'فصل‌ها در آخر', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'نگهداری', diff --git a/lang/fi/settings.php b/lang/fi/settings.php index 499122fe35b..adc47fe2d87 100644 --- a/lang/fi/settings.php +++ b/lang/fi/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Huolto', diff --git a/lang/fr/errors.php b/lang/fr/errors.php index a5a3614c2f9..b74fb5465db 100644 --- a/lang/fr/errors.php +++ b/lang/fr/errors.php @@ -13,7 +13,7 @@ 'auth_pre_register_theme_prevention' => 'Le compte utilisateur n\'a pas pu être enregistré avec les informations fournies', 'email_already_confirmed' => 'Cet e-mail a déjà été validé, vous pouvez vous connecter.', 'email_confirmation_invalid' => 'Cette confirmation est invalide. Veuillez essayer de vous inscrire à nouveau.', - 'email_confirmation_expired' => 'Le jeton de confirmation est périmé. Un nouvel e-mail vous a été envoyé.', + 'email_confirmation_expired' => 'Le jeton de confirmation a expiré. Un nouvel e-mail vous a été envoyé.', 'email_confirmation_awaiting' => 'L\'adresse e-mail du compte utilisé doit être confirmée', 'ldap_fail_anonymous' => 'L\'accès LDAP anonyme n\'a pas abouti', 'ldap_fail_authed' => 'L\'accès LDAP n\'a pas abouti avec cet utilisateur et ce mot de passe', @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => 'Le secret fourni pour le jeton d\'API utilisé est incorrect', 'api_user_no_api_permission' => 'Le propriétaire du jeton API utilisé n\'a pas la permission de passer des requêtes API', 'api_user_token_expired' => 'Le jeton d\'autorisation utilisé a expiré', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Seules les requêtes GET sont autorisées lors de l’utilisation de l’API avec une authentification basée sur les cookies', // Settings & Maintenance 'maintenance_test_email_failure' => 'Erreur émise lors de l\'envoi d\'un e-mail de test :', diff --git a/lang/fr/settings.php b/lang/fr/settings.php index 317e777b972..7b0987a488c 100644 --- a/lang/fr/settings.php +++ b/lang/fr/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapitres en premier', 'sort_rule_op_chapters_last' => 'Chapitres en dernier', 'sorting_page_limits' => 'Limite d\'affichage par page', - 'sorting_page_limits_desc' => 'Définissez le nombre d’éléments à afficher par page dans les différentes listes du système. En général, un nombre plus faible offre de meilleures performances, tandis qu’un nombre plus élevé réduit le besoin de naviguer entre plusieurs pages. Il est recommandé d’utiliser un multiple pair de 3 (18, 24, 30, etc.).', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Maintenance', diff --git a/lang/he/settings.php b/lang/he/settings.php index 0b5034475b9..46150081aa6 100644 --- a/lang/he/settings.php +++ b/lang/he/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'תחזוקה', diff --git a/lang/hr/settings.php b/lang/hr/settings.php index 6465d0ea72e..0692c8d7a13 100644 --- a/lang/hr/settings.php +++ b/lang/hr/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Održavanje', diff --git a/lang/hu/settings.php b/lang/hu/settings.php index c6810ed6838..53b1cdcc424 100644 --- a/lang/hu/settings.php +++ b/lang/hu/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Karbantartás', diff --git a/lang/id/settings.php b/lang/id/settings.php index cc942668307..8bdd99e6890 100644 --- a/lang/id/settings.php +++ b/lang/id/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Bab di Urutan Pertama', 'sort_rule_op_chapters_last' => 'Bab di Urutan Terakhir', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Pemeliharaan', diff --git a/lang/is/settings.php b/lang/is/settings.php index 5699c88d623..b1f21ac10b8 100644 --- a/lang/is/settings.php +++ b/lang/is/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Viðhald', diff --git a/lang/it/errors.php b/lang/it/errors.php index 99700088b29..1d6c716361d 100644 --- a/lang/it/errors.php +++ b/lang/it/errors.php @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => 'Il token segreto fornito per il token API utilizzato non è corretto', 'api_user_no_api_permission' => 'Il proprietario del token API utilizzato non ha il permesso di effettuare chiamate API', 'api_user_token_expired' => 'Il token di autorizzazione utilizzato è scaduto', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Solo le richieste GET sono consentite quando si utilizza l\'API con autenticazione basata sui cookie', // Settings & Maintenance 'maintenance_test_email_failure' => 'Si è verificato un errore durante l\'invio di una e-mail di prova:', diff --git a/lang/it/settings.php b/lang/it/settings.php index 88c6a4c11bf..2b5819b2aa7 100644 --- a/lang/it/settings.php +++ b/lang/it/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Capitoli Prima', 'sort_rule_op_chapters_last' => 'Capitoli dopo', 'sorting_page_limits' => 'Limiti Visualizzazione Per Pagina', - 'sorting_page_limits_desc' => 'Imposta il numero di elementi da visualizzare per pagina nei vari elenchi all\'interno del sistema. In genere, un numero inferiore garantisce prestazioni migliori, mentre un numero più elevato evita la necessità di cliccare su più pagine. Si consiglia di utilizzare un multiplo pari di 3 (18, 24, 30, ecc...).', + 'sorting_page_limits_desc' => 'Imposta il numero di elementi da visualizzare per pagina nei vari elenchi del sistema. In genere, un numero inferiore garantisce prestazioni migliori, mentre un numero maggiore evita di dover sfogliare più pagine. Si consiglia di utilizzare un multiplo di 6.', // Maintenance settings 'maint' => 'Manutenzione', diff --git a/lang/ja/errors.php b/lang/ja/errors.php index 7ce8db11100..0c1c8d84ad5 100644 --- a/lang/ja/errors.php +++ b/lang/ja/errors.php @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => '利用されたAPIトークンに対して提供されたシークレットが正しくありません', 'api_user_no_api_permission' => '使用されているAPIトークンの所有者には、API呼び出しを行う権限がありません', 'api_user_token_expired' => '認証トークンが期限切れです。', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Cookie ベースの認証で API を使用する場合、GET リクエストのみが許可されます', // Settings & Maintenance 'maintenance_test_email_failure' => 'テストメール送信時にエラーが発生しました:', diff --git a/lang/ja/settings.php b/lang/ja/settings.php index 53b14233e71..717a4c10ffc 100644 --- a/lang/ja/settings.php +++ b/lang/ja/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'チャプタを最初に', 'sort_rule_op_chapters_last' => 'チャプタを最後に', 'sorting_page_limits' => 'ページング表示制限', - 'sorting_page_limits_desc' => 'システム内の各種リストで1ページに表示するアイテム数を設定します。 通常、少ない数に設定するとパフォーマンスが向上し、多い数に設定するとページの移動操作が少なくなります。 3の倍数(18、24、30など)を使用することをお勧めします。', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'メンテナンス', diff --git a/lang/ka/settings.php b/lang/ka/settings.php index c68605fe1f8..c4d1eb136eb 100644 --- a/lang/ka/settings.php +++ b/lang/ka/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Maintenance', diff --git a/lang/ko/settings.php b/lang/ko/settings.php index 0488bfe140e..97af673c6af 100644 --- a/lang/ko/settings.php +++ b/lang/ko/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => '챕터 우선 정렬', 'sort_rule_op_chapters_last' => '챕터 나중 정렬', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => '유지관리', diff --git a/lang/ku/settings.php b/lang/ku/settings.php index c68605fe1f8..c4d1eb136eb 100644 --- a/lang/ku/settings.php +++ b/lang/ku/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Maintenance', diff --git a/lang/lt/settings.php b/lang/lt/settings.php index bcc7c82bd70..f797e567ec1 100644 --- a/lang/lt/settings.php +++ b/lang/lt/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Priežiūra', diff --git a/lang/lv/settings.php b/lang/lv/settings.php index fb5cf13ddee..9dc6bf402bd 100644 --- a/lang/lv/settings.php +++ b/lang/lv/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Nodaļas pirmās', 'sort_rule_op_chapters_last' => 'Nodaļas pēdējās', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Apkope', diff --git a/lang/nb/settings.php b/lang/nb/settings.php index 61b1c33671c..5fcaaaca6c1 100644 --- a/lang/nb/settings.php +++ b/lang/nb/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Kapitler først', 'sort_rule_op_chapters_last' => 'Kapitler sist', 'sorting_page_limits' => 'Visningsgrenser for hver side', - 'sorting_page_limits_desc' => 'Angi hvor mange elementer som skal vises på hver side i ulike lister i systemet. Et lavere antall vil vanligvis gi bedre ytelse, mens et høyere antall reduserer behovet for å bla gjennom mange sider. Det er anbefalt å bruke en multiplikasjon av 3 som gir partall (18, 24, 30 osv.).', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Vedlikehold', diff --git a/lang/ne/settings.php b/lang/ne/settings.php index 37e59978e1f..dbc7d8e9fc9 100644 --- a/lang/ne/settings.php +++ b/lang/ne/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'पहिले अध्यायहरू', 'sort_rule_op_chapters_last' => 'अन्त्यमा अध्यायहरू', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'सम्भार', diff --git a/lang/nl/settings.php b/lang/nl/settings.php index 63805f4983c..c8d071119ff 100644 --- a/lang/nl/settings.php +++ b/lang/nl/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Hoofdstukken Eerst', 'sort_rule_op_chapters_last' => 'Hoofdstukken Laatst', 'sorting_page_limits' => 'Weergavelimiet Per Pagina', - 'sorting_page_limits_desc' => 'Stel in hoeveel items er op een pagina worden laten zien in de verschillende lijstweergaves. Een lager aantal verbeterd de snelheid, een hoger aantal verminderd het doorklikken door pagina\'s. Een even veelvoud van 3 (18, 24, 30, etc...) wordt aanbevolen.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Onderhoud', diff --git a/lang/nn/settings.php b/lang/nn/settings.php index 6d2259bd88d..e4b6e6af93d 100644 --- a/lang/nn/settings.php +++ b/lang/nn/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Vedlikehold', diff --git a/lang/pl/settings.php b/lang/pl/settings.php index 5954b005f09..775d4f25d2c 100644 --- a/lang/pl/settings.php +++ b/lang/pl/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Rozdziały na początku', 'sort_rule_op_chapters_last' => 'Rozdziały na końcu', 'sorting_page_limits' => 'Limity wyświetlania per strona', - 'sorting_page_limits_desc' => 'Ustaw ile elementów pokazywać per strona w różnych listach w systemie. Zazwyczaj mniejsza ilość będzie bardziej wydajna, podczas gdy większa ilość unika konieczności przeglądania wielu stron. Zaleca się stosowanie parzystej wielokrotności 3 (18, 24, 30 itp...).', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Konserwacja', diff --git a/lang/pt/settings.php b/lang/pt/settings.php index c2179e64027..a59335b7cee 100644 --- a/lang/pt/settings.php +++ b/lang/pt/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Manutenção', diff --git a/lang/pt_BR/errors.php b/lang/pt_BR/errors.php index 4d711076f46..3287154331b 100644 --- a/lang/pt_BR/errors.php +++ b/lang/pt_BR/errors.php @@ -110,7 +110,7 @@ 'import_zip_cant_read' => 'Não foi possível ler o arquivo ZIP.', 'import_zip_cant_decode_data' => 'Não foi possível encontrar e decodificar o conteúdo ZIP data.json.', 'import_zip_no_data' => 'Os dados do arquivo ZIP não têm o conteúdo esperado livro, capítulo ou página.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'O conteúdo ZIP data.json excede o tamanho máximo de upload configurado para a aplicação.', 'import_validation_failed' => 'Falhou na validação da importação do ZIP com erros:', 'import_zip_failed_notification' => 'Falhou ao importar arquivo ZIP.', 'import_perms_books' => 'Você não tem as permissões necessárias para criar livros.', @@ -126,7 +126,7 @@ 'api_incorrect_token_secret' => 'O segredo fornecido para o código de API usado está incorreto', 'api_user_no_api_permission' => 'O proprietário do código de API utilizado não tem permissão para fazer requisições de API', 'api_user_token_expired' => 'O código de autenticação expirou', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Somente solicitações GET são permitidas ao usar a API com autenticação baseada em cookies', // Settings & Maintenance 'maintenance_test_email_failure' => 'Erro encontrado ao enviar uma mensagem eletrônica de teste:', diff --git a/lang/pt_BR/notifications.php b/lang/pt_BR/notifications.php index 8c98467c81d..7f6c100b1d3 100644 --- a/lang/pt_BR/notifications.php +++ b/lang/pt_BR/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => 'Página atualizada: :pageName', 'updated_page_intro' => 'Uma página foi atualizada em :appName:', 'updated_page_debounce' => 'Para prevenir notificações em massa, por enquanto notificações não serão enviadas para você para próximas edições nessa página pelo mesmo editor.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'Você foi mencionado em um comentário na página: :pageName', + 'comment_mention_intro' => 'Você foi mencionado em um comentário sobre :appName:', 'detail_page_name' => 'Nome da Página:', 'detail_page_path' => 'Caminho da Página:', diff --git a/lang/pt_BR/preferences.php b/lang/pt_BR/preferences.php index d2b7fc540a0..a92d971c3c3 100644 --- a/lang/pt_BR/preferences.php +++ b/lang/pt_BR/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'Controle as notificações por e-mail que você recebe quando uma determinada atividade é executada no sistema.', 'notifications_opt_own_page_changes' => 'Notificar quando houver alterações em páginas que eu possuo', 'notifications_opt_own_page_comments' => 'Notificar comentários nas páginas que eu possuo', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'Notificar quando eu for mencionado em um comentário', 'notifications_opt_comment_replies' => 'Notificar ao responder aos meus comentários', 'notifications_save' => 'Salvar Preferências', 'notifications_update_success' => 'Preferências de notificação foram atualizadas!', diff --git a/lang/pt_BR/settings.php b/lang/pt_BR/settings.php index 53947a36da6..97b434727f8 100644 --- a/lang/pt_BR/settings.php +++ b/lang/pt_BR/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Capítulos Primeiro', 'sort_rule_op_chapters_last' => 'Capítulos por Último', 'sorting_page_limits' => 'Limites de exibição por página', - 'sorting_page_limits_desc' => 'Defina quantos itens serão exibidos por página em diferentes listas do sistema. Normalmente, um número menor proporciona melhor desempenho, enquanto um número maior evita a necessidade de clicar em várias páginas. É recomendado o uso de um múltiplo par de 3 (18, 24, 30, etc.).', + 'sorting_page_limits_desc' => 'Defina quantos itens mostrar por página em várias listas no sistema. Normalmente, uma quantidade menor será mais eficiente, enquanto uma quantidade maior evita a necessidade de clicar em várias páginas. Recomenda-se usar um múltiplo de 6.', // Maintenance settings 'maint' => 'Manutenção', @@ -197,13 +197,13 @@ 'role_import_content' => 'Importar conteúdo', 'role_editor_change' => 'Alterar página de edição', 'role_notifications' => 'Receber e gerenciar notificações', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => 'Essas permissões tecnicamente também fornecerão visibilidade e busca de usuários e perfis no sistema.', 'role_asset' => 'Permissões de Ativos', 'roles_system_warning' => 'Esteja ciente de que o acesso a qualquer uma das três permissões acima pode permitir que um usuário altere seus próprios privilégios ou privilégios de outros usuários no sistema. Apenas atribua perfis com essas permissões para usuários confiáveis.', 'role_asset_desc' => 'Essas permissões controlam o acesso padrão para os ativos dentro do sistema. Permissões em Livros, Capítulos e Páginas serão sobrescritas por essas permissões.', 'role_asset_admins' => 'Administradores recebem automaticamente acesso a todo o conteúdo, mas essas opções podem mostrar ou ocultar as opções da Interface de Usuário.', 'role_asset_image_view_note' => 'Isso está relacionado à visibilidade no gerenciador de imagens. O acesso real dos arquivos de imagem carregados dependerá da opção de armazenamento de imagem do sistema.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Essas permissões tecnicamente também fornecerão visibilidade e busca de usuários do sistema.', 'role_all' => 'Todos', 'role_own' => 'Próprio', 'role_controlled_by_asset' => 'Controlado pelos ativos nos quais o upload foi realizado', diff --git a/lang/pt_BR/validation.php b/lang/pt_BR/validation.php index e30ebc3778e..4d8139c4b5c 100644 --- a/lang/pt_BR/validation.php +++ b/lang/pt_BR/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'O arquivo não pôde ser carregado. O servidor pode não aceitar arquivos deste tamanho.', 'zip_file' => 'O :attribute precisa fazer referência a um arquivo do ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => 'O arquivo :attribute não deve exceder :size MB.', 'zip_file_mime' => 'O :attribute precisa fazer referência a um arquivo do tipo :validTypes, encontrado :foundType.', 'zip_model_expected' => 'Objeto de dados esperado, mas ":type" encontrado.', 'zip_unique' => 'O :attribute deve ser único para o tipo de objeto dentro do ZIP.', diff --git a/lang/ro/settings.php b/lang/ro/settings.php index 02052ef3cd6..d65a8e0714f 100644 --- a/lang/ro/settings.php +++ b/lang/ro/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Mentenanţă', diff --git a/lang/ru/entities.php b/lang/ru/entities.php index 50f061115d9..bf224c10a91 100644 --- a/lang/ru/entities.php +++ b/lang/ru/entities.php @@ -39,21 +39,21 @@ 'export_pdf' => 'PDF файл', 'export_text' => 'Текстовый файл', 'export_md' => 'Файл Markdown', - 'export_zip' => 'Portable ZIP', + 'export_zip' => 'Портативный ZIP', 'default_template' => 'Шаблон страницы по умолчанию', 'default_template_explain' => 'Назначить шаблон страницы, который будет использоваться в качестве содержимого по умолчанию для всех страниц, созданных в этом элементе. Имейте в виду, что это будет работать, только если создатель страницы имеет доступ к выбранной странице шаблона.', 'default_template_select' => 'Выберите страницу шаблона', 'import' => 'Импорт', - 'import_validate' => 'Validate Import', + 'import_validate' => 'Проверка импорта', 'import_desc' => 'Импортировать книги, главы и страницы с помощью ZIP-файла, экспортированного из этого или другого источника. Выберите ZIP-файл, чтобы продолжить. После загрузки и проверки файла вы сможете настроить и подтвердить импорт в следующем окне.', - 'import_zip_select' => 'Select ZIP file to upload', - 'import_zip_validation_errors' => 'Errors were detected while validating the provided ZIP file:', - 'import_pending' => 'Pending Imports', - 'import_pending_none' => 'No imports have been started.', - 'import_continue' => 'Continue Import', + 'import_zip_select' => 'Выберите ZIP файл для загрузки', + 'import_zip_validation_errors' => 'Были обнаружены ошибки при проверке предоставленного ZIP файла:', + 'import_pending' => 'Ожидается импорт', + 'import_pending_none' => 'Импорт не был запущен.', + 'import_continue' => 'Продолжить импорт', 'import_continue_desc' => 'Review the content due to be imported from the uploaded ZIP file. When ready, run the import to add its contents to this system. The uploaded ZIP import file will be automatically removed on successful import.', 'import_details' => 'Import Details', - 'import_run' => 'Run Import', + 'import_run' => 'Запустить импорт', 'import_size' => ':size Import ZIP Size', 'import_uploaded_at' => 'Uploaded :relativeTime', 'import_uploaded_by' => 'Uploaded by', @@ -61,7 +61,7 @@ 'import_location_desc' => 'Select a target location for your imported content. You\'ll need the relevant permissions to create within the location you choose.', 'import_delete_confirm' => 'Are you sure you want to delete this import?', 'import_delete_desc' => 'This will delete the uploaded import ZIP file, and cannot be undone.', - 'import_errors' => 'Import Errors', + 'import_errors' => 'Ошибки импорта', 'import_errors_desc' => 'The follow errors occurred during the import attempt:', 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter', @@ -252,7 +252,7 @@ 'pages_edit_switch_to_markdown_stable' => 'Полное сохранение форматирования (HTML)', 'pages_edit_switch_to_wysiwyg' => 'Переключиться в WYSIWYG', 'pages_edit_switch_to_new_wysiwyg' => 'Switch to new WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(В бета-тестировании)', 'pages_edit_set_changelog' => 'Задать список изменений', 'pages_edit_enter_changelog_desc' => 'Введите краткое описание внесенных изменений', 'pages_edit_enter_changelog' => 'Введите список изменений', @@ -397,11 +397,11 @@ 'comment' => 'Комментарий', 'comments' => 'Комментарии', 'comment_add' => 'Комментировать', - 'comment_none' => 'No comments to display', + 'comment_none' => 'Нет комментариев для отображения', 'comment_placeholder' => 'Оставить комментарий здесь', 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', + 'comment_archived_count' => ':count архивировано', + 'comment_archived_threads' => 'Архивированные темы', 'comment_save' => 'Сохранить комментарий', 'comment_new' => 'Новый комментарий', 'comment_created' => 'прокомментировал :createDiff', @@ -410,14 +410,14 @@ 'comment_deleted_success' => 'Комментарий удален', 'comment_created_success' => 'Комментарий добавлен', 'comment_updated_success' => 'Комментарий обновлен', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', + 'comment_archive_success' => 'Комментарий заархивирован', + 'comment_unarchive_success' => 'Комментарий разархивирован', + 'comment_view' => 'Просмотреть комментарий', + 'comment_jump_to_thread' => 'Перейти к теме', 'comment_delete_confirm' => 'Удалить этот комментарий?', 'comment_in_reply_to' => 'В ответ на :commentId', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', + 'comment_reference' => 'Ссылка', + 'comment_reference_outdated' => '(Устаревшее)', 'comment_editor_explain' => 'Вот комментарии, которые были оставлены на этой странице. Комментарии могут быть добавлены и управляться при просмотре сохраненной страницы.', // Revision diff --git a/lang/ru/notifications.php b/lang/ru/notifications.php index 289de42b6c0..96e853723b7 100644 --- a/lang/ru/notifications.php +++ b/lang/ru/notifications.php @@ -11,7 +11,7 @@ 'updated_page_subject' => 'Обновлена страница: :pageName', 'updated_page_intro' => 'Страница была обновлена в :appName:', 'updated_page_debounce' => 'Чтобы предотвратить массовые уведомления, в течение некоторого времени вы не будете получать уведомления о дальнейших правках этой страницы этим же редактором.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', + 'comment_mention_subject' => 'Вы были упомянуты в комментарии на странице: :pageName', 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', 'detail_page_name' => 'Имя страницы:', diff --git a/lang/ru/settings.php b/lang/ru/settings.php index 47839d52006..76a2eebbf1e 100644 --- a/lang/ru/settings.php +++ b/lang/ru/settings.php @@ -75,7 +75,7 @@ 'reg_confirm_restrict_domain_placeholder' => 'Без ограничений', // Sorting Settings - 'sorting' => 'Lists & Sorting', + 'sorting' => 'Списки и сортировка', 'sorting_book_default' => 'Default Book Sort Rule', 'sorting_book_default_desc' => 'Выберите правило сортировки по умолчанию для новых книг. Это не повлияет на существующие книги, и может быть изменено для каждой книги отдельно.', 'sorting_rules' => 'Правила сортировки', @@ -101,10 +101,10 @@ 'sort_rule_op_name_numeric' => 'По нумерации', 'sort_rule_op_created_date' => 'Created Date', 'sort_rule_op_updated_date' => 'Updated Date', - 'sort_rule_op_chapters_first' => 'Chapters First', + 'sort_rule_op_chapters_first' => 'Главы в начале', 'sort_rule_op_chapters_last' => 'Главы в конце', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Обслуживание', diff --git a/lang/sk/settings.php b/lang/sk/settings.php index 04855a7f96e..67671f6f82c 100644 --- a/lang/sk/settings.php +++ b/lang/sk/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Údržba', diff --git a/lang/sl/settings.php b/lang/sl/settings.php index 6eaed0a1702..947621389f4 100644 --- a/lang/sl/settings.php +++ b/lang/sl/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Vzdrževanje', diff --git a/lang/sq/settings.php b/lang/sq/settings.php index c68605fe1f8..c4d1eb136eb 100644 --- a/lang/sq/settings.php +++ b/lang/sq/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Maintenance', diff --git a/lang/sr/settings.php b/lang/sr/settings.php index d34ff3f3b7d..f6c86827e69 100644 --- a/lang/sr/settings.php +++ b/lang/sr/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Одржавање', diff --git a/lang/sv/entities.php b/lang/sv/entities.php index 08c44ff1fe1..680e0908aa7 100644 --- a/lang/sv/entities.php +++ b/lang/sv/entities.php @@ -397,7 +397,7 @@ 'comment' => 'Kommentar', 'comments' => 'Kommentarer', 'comment_add' => 'Lägg till kommentar', - 'comment_none' => 'No comments to display', + 'comment_none' => 'Inga kommentarer att visa', 'comment_placeholder' => 'Lämna en kommentar här', 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', 'comment_archived_count' => ':count Archived', diff --git a/lang/sv/preferences.php b/lang/sv/preferences.php index 492081e59e3..7ebf2681350 100644 --- a/lang/sv/preferences.php +++ b/lang/sv/preferences.php @@ -5,7 +5,7 @@ */ return [ - 'my_account' => 'My Account', + 'my_account' => 'Mitt Konto', 'shortcuts' => 'Genvägar', 'shortcuts_interface' => 'UI Shortcut Preferences', diff --git a/lang/sv/settings.php b/lang/sv/settings.php index 2e86241dae4..773c4bff35e 100644 --- a/lang/sv/settings.php +++ b/lang/sv/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Underhåll', diff --git a/lang/tk/settings.php b/lang/tk/settings.php index c68605fe1f8..c4d1eb136eb 100644 --- a/lang/tk/settings.php +++ b/lang/tk/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Maintenance', diff --git a/lang/tr/settings.php b/lang/tr/settings.php index 71d56000fc1..a33d3e0ac04 100644 --- a/lang/tr/settings.php +++ b/lang/tr/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Bakım', diff --git a/lang/uk/settings.php b/lang/uk/settings.php index 55966c01c57..afeb2c48928 100644 --- a/lang/uk/settings.php +++ b/lang/uk/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Спочатку розділи', 'sort_rule_op_chapters_last' => 'Розділи останні', 'sorting_page_limits' => 'Обмеження відображення сторінок', - 'sorting_page_limits_desc' => 'Кількість елементів для відображення в різних списках в системі. Зазвичай менша кількість буде більш продуктивною, в той час як більша кількість уникає необхідність натискання на кілька сторінок. Рекомендується використовувати парне кратне 3 (18, 24, 30 тощо).', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Обслуговування', diff --git a/lang/uz/settings.php b/lang/uz/settings.php index ad191143f40..259aee71a3f 100644 --- a/lang/uz/settings.php +++ b/lang/uz/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapters First', 'sort_rule_op_chapters_last' => 'Chapters Last', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Xizmat', diff --git a/lang/vi/settings.php b/lang/vi/settings.php index 69eadddd36f..f5f2377c81b 100644 --- a/lang/vi/settings.php +++ b/lang/vi/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chương trước', 'sort_rule_op_chapters_last' => 'Chương sau', 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => 'Bảo trì', diff --git a/lang/zh_CN/editor.php b/lang/zh_CN/editor.php index 6dbca56e313..4bac4cf0d6b 100644 --- a/lang/zh_CN/editor.php +++ b/lang/zh_CN/editor.php @@ -48,7 +48,7 @@ 'superscript' => '上标', 'subscript' => '下标', 'text_color' => '文本颜色', - 'highlight_color' => 'Highlight color', + 'highlight_color' => '高亮颜色', 'custom_color' => '自定义颜色', 'remove_color' => '移除颜色', 'background_color' => '背景色', diff --git a/lang/zh_CN/entities.php b/lang/zh_CN/entities.php index 826a8ec1e65..c4ec0414dab 100644 --- a/lang/zh_CN/entities.php +++ b/lang/zh_CN/entities.php @@ -63,10 +63,10 @@ 'import_delete_desc' => '这将删除上传的ZIP文件,不能撤消。', 'import_errors' => '导入错误', 'import_errors_desc' => '在尝试导入过程中出现了以下错误:', - 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', - 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter', - 'breadcrumb_siblings_for_book' => 'Navigate siblings for book', - 'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf', + 'breadcrumb_siblings_for_page' => '导航页面', + 'breadcrumb_siblings_for_chapter' => '导航章节', + 'breadcrumb_siblings_for_book' => '导航书籍', + 'breadcrumb_siblings_for_bookshelf' => '导航书架', // Permissions and restrictions 'permissions' => '权限', @@ -399,7 +399,7 @@ 'comment_add' => '添加评论', 'comment_none' => '没有要显示的评论', 'comment_placeholder' => '在这里评论', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', + 'comment_thread_count' => ':count 条', 'comment_archived_count' => ':count 条评论已存档', 'comment_archived_threads' => '已存档的贴子', 'comment_save' => '保存评论', diff --git a/lang/zh_CN/errors.php b/lang/zh_CN/errors.php index 74814c6b0c0..98b26407233 100644 --- a/lang/zh_CN/errors.php +++ b/lang/zh_CN/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => '无法读取 ZIP 文件。', 'import_zip_cant_decode_data' => '无法找到并解码 ZIP data.json 内容。', 'import_zip_no_data' => 'ZIP 文件数据没有预期的书籍、章节或页面内容。', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => '超出最大上传大小。', 'import_validation_failed' => '导入 ZIP 验证失败,出现错误:', 'import_zip_failed_notification' => 'ZIP 文件导入失败。', 'import_perms_books' => '您缺少创建书籍所需的权限。', @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => '给已给出的API所提供的密钥不正确', 'api_user_no_api_permission' => '使用过的 API 令牌的所有者没有进行API 调用的权限', 'api_user_token_expired' => '所使用的身份令牌已过期', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => '使用基于 Cookie 的身份验证 API 时,仅允许 GET 请求。', // Settings & Maintenance 'maintenance_test_email_failure' => '发送测试电子邮件时出现错误:', diff --git a/lang/zh_CN/notifications.php b/lang/zh_CN/notifications.php index e4eebf5cc56..55fa6824a5a 100644 --- a/lang/zh_CN/notifications.php +++ b/lang/zh_CN/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => '页面更新::pageName', 'updated_page_intro' => ':appName: 中的一个页面已被更新', 'updated_page_debounce' => '为了防止出现大量通知,一段时间内您不会收到同一编辑者再次编辑本页面的通知。', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => '在页面中被提及::pageName', + 'comment_mention_intro' => '在 :appName 中被提及:', 'detail_page_name' => '页面名称:', 'detail_page_path' => '页面路径:', diff --git a/lang/zh_CN/preferences.php b/lang/zh_CN/preferences.php index f89448dd366..0a1f165bbac 100644 --- a/lang/zh_CN/preferences.php +++ b/lang/zh_CN/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => '控制在系统内发生某些活动时您会收到的电子邮件通知。', 'notifications_opt_own_page_changes' => '在我拥有的页面被修改时通知我', 'notifications_opt_own_page_comments' => '在我拥有的页面上有新评论时通知我', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => '当我在评论中被提及时通知我', 'notifications_opt_comment_replies' => '在有人回复我的频率时通知我', 'notifications_save' => '保存偏好设置', 'notifications_update_success' => '通知偏好设置已更新!', diff --git a/lang/zh_CN/settings.php b/lang/zh_CN/settings.php index 3469752bfce..e53e67aba32 100644 --- a/lang/zh_CN/settings.php +++ b/lang/zh_CN/settings.php @@ -75,8 +75,8 @@ 'reg_confirm_restrict_domain_placeholder' => '尚未设置限制', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', + 'sorting' => '列表和排序', + 'sorting_book_default' => '默认排序规则', 'sorting_book_default_desc' => '选择要应用于新书的默认排序规则。这不会影响现有书,并且可以每本书覆盖。', 'sorting_rules' => '排序规则', 'sorting_rules_desc' => '这些是预定义的排序操作,可应用于系统中的内容。', @@ -103,8 +103,8 @@ 'sort_rule_op_updated_date' => '更新时间', 'sort_rule_op_chapters_first' => '章节正序', 'sort_rule_op_chapters_last' => '章节倒序', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.', + 'sorting_page_limits' => '每页显示限制', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => '维护', @@ -197,13 +197,13 @@ 'role_import_content' => '导入内容', 'role_editor_change' => '更改页面编辑器', 'role_notifications' => '管理和接收通知', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => '从技术上讲,这些权限还将提供对系统中用户和角色的可见性和搜索功能。', 'role_asset' => '资源许可', 'roles_system_warning' => '请注意,拥有以上三个权限中的任何一个都会允许用户更改自己的权限或系统中其他人的权限。 请只将拥有这些权限的角色分配给你信任的用户。', 'role_asset_desc' => '对系统内资源的默认访问许可将由这些权限控制。单独设置在书籍、章节和页面上的权限将覆盖这里的权限设定。', 'role_asset_admins' => '管理员可自动获得对所有内容的访问权限,但这些选项可能会显示或隐藏UI选项。', 'role_asset_image_view_note' => '这与图像管理器中的可见性有关。已经上传的图片的实际访问取决于系统图像存储选项。', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => '从技术上讲,这些权限还将提供对系统中用户和角色的可见性和搜索功能。', 'role_all' => '全部的', 'role_own' => '拥有的', 'role_controlled_by_asset' => '由其所在的资源来控制', diff --git a/lang/zh_CN/validation.php b/lang/zh_CN/validation.php index 748c8f56771..955381ccbab 100644 --- a/lang/zh_CN/validation.php +++ b/lang/zh_CN/validation.php @@ -106,7 +106,7 @@ 'uploaded' => '无法上传文件。 服务器可能不接受此大小的文件。', 'zip_file' => ':attribute 需要引用 ZIP 内的文件。', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => ':attribute 不能超过 :size MB 。', 'zip_file_mime' => ':attribute 需要引用类型为 :validTypes 的文件,找到 :foundType 。', 'zip_model_expected' => '预期的数据对象,但找到了 ":type" 。', 'zip_unique' => '对于 ZIP 中的对象类型来说,:attribute 必须是唯一的。', diff --git a/lang/zh_TW/settings.php b/lang/zh_TW/settings.php index 0d5d760dd29..65778f77ca3 100644 --- a/lang/zh_TW/settings.php +++ b/lang/zh_TW/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => '第一章', 'sort_rule_op_chapters_last' => '最後一章', 'sorting_page_limits' => '每頁顯示限制', - 'sorting_page_limits_desc' => '設定系統內各類清單每頁顯示的項目數量。通常較低的數量能提升效能表現,而較高的數量則可避免使用者需點擊翻閱多頁。建議採用 3 的整數倍數(如 18、24、30 等)。', + 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings 'maint' => '維護', From 362859ac23761631949089de61df338ed002ca97 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Mar 2026 13:14:54 +0000 Subject: [PATCH 083/204] Updated translator & dependency attribution before release v26.03 --- .github/translators.txt | 3 +++ dev/licensing/js-library-licenses.txt | 19 ------------------- dev/licensing/php-library-licenses.txt | 2 +- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/.github/translators.txt b/.github/translators.txt index 14c51bd0b32..fcbb9675dda 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -530,3 +530,6 @@ Shadluk Avan (quldosh) :: Uzbek Marci (MartonPoto) :: Hungarian Michał Sadurski (wheeskeey) :: Polish JanDziaslo :: Polish +Charllys Fernandes (CharllysFernandes) :: Portuguese, Brazilian +Ilgiz Zigangirov (inov8) :: Russian +Max Israelsson (Blezie) :: Swedish diff --git a/dev/licensing/js-library-licenses.txt b/dev/licensing/js-library-licenses.txt index d7ad4ecc8f2..1c23c093762 100644 --- a/dev/licensing/js-library-licenses.txt +++ b/dev/licensing/js-library-licenses.txt @@ -3321,20 +3321,6 @@ Copyright: Copyright 2022 Romain Menke, Antonio Laguna <*******@******.**> Source: git+https://github.com/csstools/postcss-plugins.git Link: https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer#readme ----------- -@emnapi/core -License: MIT -License File: node_modules/@emnapi/core/LICENSE -Copyright: Copyright (c) 2021-present Toyobayashi -Source: git+https://github.com/toyobayashi/emnapi.git -Link: https://github.com/toyobayashi/emnapi#readme ------------ -@emnapi/runtime -License: MIT -License File: node_modules/@emnapi/runtime/LICENSE -Copyright: Copyright (c) 2021-present Toyobayashi -Source: git+https://github.com/toyobayashi/emnapi.git -Link: https://github.com/toyobayashi/emnapi#readme ------------ @esbuild/linux-x64 License: MIT Source: git+https://github.com/evanw/esbuild.git @@ -3784,11 +3770,6 @@ Copyright: Copyright (c) Microsoft Corporation. Source: https://github.com/tsconfig/bases.git Link: https://github.com/tsconfig/bases.git ----------- -@tybys/wasm-util -License: MIT -Source: https://github.com/toyobayashi/wasm-util.git -Link: https://github.com/toyobayashi/wasm-util.git ------------ @types/babel__core License: MIT License File: node_modules/@types/babel__core/LICENSE diff --git a/dev/licensing/php-library-licenses.txt b/dev/licensing/php-library-licenses.txt index 6ae0b86b936..348fce2ff98 100644 --- a/dev/licensing/php-library-licenses.txt +++ b/dev/licensing/php-library-licenses.txt @@ -8,7 +8,7 @@ aws/aws-sdk-php License: Apache-2.0 License File: vendor/aws/aws-sdk-php/LICENSE Source: https://github.com/aws/aws-sdk-php.git -Link: http://aws.amazon.com/sdkforphp +Link: https://aws.amazon.com/sdk-for-php ----------- bacon/bacon-qr-code License: BSD-2-Clause From 4f18fea08691bc0cfb2beab0e4495c16f1c8bdce Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Mar 2026 13:17:48 +0000 Subject: [PATCH 084/204] Deps: Updated PHP deps pre v26.03 release --- composer.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/composer.lock b/composer.lock index d8ea0066265..030cd1a7397 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.373.0", + "version": "3.373.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "fb74a2dca7ae2363e929c5cea33a4a4db0d22690" + "reference": "483fba51c28b3a0c0647bf5100e0edca82090b18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/fb74a2dca7ae2363e929c5cea33a4a4db0d22690", - "reference": "fb74a2dca7ae2363e929c5cea33a4a4db0d22690", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/483fba51c28b3a0c0647bf5100e0edca82090b18", + "reference": "483fba51c28b3a0c0647bf5100e0edca82090b18", "shasum": "" }, "require": { @@ -92,12 +92,12 @@ "aws/aws-php-sns-message-validator": "~1.0", "behat/behat": "~3.0", "composer/composer": "^2.7.8", - "dms/phpunit-arraysubset-asserts": "^0.4.0", + "dms/phpunit-arraysubset-asserts": "^v0.5.0", "doctrine/cache": "~1.4", "ext-dom": "*", "ext-openssl": "*", "ext-sockets": "*", - "phpunit/phpunit": "^9.6", + "phpunit/phpunit": "^10.0", "psr/cache": "^2.0 || ^3.0", "psr/simple-cache": "^2.0 || ^3.0", "sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0", @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.373.0" + "source": "https://github.com/aws/aws-sdk-php/tree/3.373.2" }, - "time": "2026-03-11T18:33:36+00:00" + "time": "2026-03-13T18:08:30+00:00" }, { "name": "bacon/bacon-qr-code", @@ -4941,16 +4941,16 @@ }, { "name": "robrichards/xmlseclibs", - "version": "3.1.4", + "version": "3.1.5", "source": { "type": "git", "url": "https://github.com/robrichards/xmlseclibs.git", - "reference": "bc87389224c6de95802b505e5265b0ec2c5bcdbd" + "reference": "03062be78178cbb5e8f605cd255dc32a14981f92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/bc87389224c6de95802b505e5265b0ec2c5bcdbd", - "reference": "bc87389224c6de95802b505e5265b0ec2c5bcdbd", + "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/03062be78178cbb5e8f605cd255dc32a14981f92", + "reference": "03062be78178cbb5e8f605cd255dc32a14981f92", "shasum": "" }, "require": { @@ -4977,9 +4977,9 @@ ], "support": { "issues": "https://github.com/robrichards/xmlseclibs/issues", - "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.4" + "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.5" }, - "time": "2025-12-08T11:57:53+00:00" + "time": "2026-03-13T10:31:56+00:00" }, { "name": "sabberworm/php-css-parser", From a9ffd3e0c77019916bba21830d8514cca723ce17 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 16 Mar 2026 18:28:44 +0000 Subject: [PATCH 085/204] Responses: Added extra sanitization for download names From testing, don't think this could exploited directly, as the response would error instead of allowing control characters, but this adds an extra layer of sanitization, and switches to encoded disposition filenames for better UTF8 support. --- app/Http/DownloadResponseFactory.php | 7 +++++-- tests/Api/ExportsApiTest.php | 30 ++++++++++++++-------------- tests/Exports/HtmlExportTest.php | 6 +++--- tests/Exports/MarkdownExportTest.php | 2 +- tests/Exports/PdfExportTest.php | 6 +++--- tests/Exports/TextExportTest.php | 6 +++--- tests/Uploads/AttachmentTest.php | 19 ++++++++++++++++-- 7 files changed, 47 insertions(+), 29 deletions(-) diff --git a/app/Http/DownloadResponseFactory.php b/app/Http/DownloadResponseFactory.php index 8384484ad62..1b6256c00ef 100644 --- a/app/Http/DownloadResponseFactory.php +++ b/app/Http/DownloadResponseFactory.php @@ -102,12 +102,15 @@ public function streamedFileInline(string $filePath, ?string $fileName = null): protected function getHeaders(string $fileName, int $fileSize, string $mime = 'application/octet-stream'): array { $disposition = ($mime === 'application/octet-stream') ? 'attachment' : 'inline'; - $downloadName = str_replace('"', '', $fileName); + + $downloadName = str_replace(['"', '/', '\\', '$'], '', $fileName); + $downloadName = preg_replace('/[\x00-\x1F\x7F]/', '', $downloadName); + $encodedDownloadName = rawurlencode($downloadName); return [ 'Content-Type' => $mime, 'Content-Length' => $fileSize, - 'Content-Disposition' => "{$disposition}; filename=\"{$downloadName}\"", + 'Content-Disposition' => "{$disposition}; filename*=UTF-8''{$encodedDownloadName}", 'X-Content-Type-Options' => 'nosniff', ]; } diff --git a/tests/Api/ExportsApiTest.php b/tests/Api/ExportsApiTest.php index e1ac698d0a4..7951e04d272 100644 --- a/tests/Api/ExportsApiTest.php +++ b/tests/Api/ExportsApiTest.php @@ -19,7 +19,7 @@ public function test_book_html_endpoint() $resp = $this->get("/api/books/{$book->id}/export/html"); $resp->assertStatus(200); $resp->assertSee($book->name); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.html"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $book->slug . '.html'); } public function test_book_plain_text_endpoint() @@ -30,7 +30,7 @@ public function test_book_plain_text_endpoint() $resp = $this->get("/api/books/{$book->id}/export/plaintext"); $resp->assertStatus(200); $resp->assertSee($book->name); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.txt"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $book->slug . '.txt'); } public function test_book_pdf_endpoint() @@ -40,7 +40,7 @@ public function test_book_pdf_endpoint() $resp = $this->get("/api/books/{$book->id}/export/pdf"); $resp->assertStatus(200); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.pdf"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $book->slug . '.pdf'); } public function test_book_markdown_endpoint() @@ -50,7 +50,7 @@ public function test_book_markdown_endpoint() $resp = $this->get("/api/books/{$book->id}/export/markdown"); $resp->assertStatus(200); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.md"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $book->slug . '.md'); $resp->assertSee('# ' . $book->name); $resp->assertSee('# ' . $book->pages()->first()->name); $resp->assertSee('# ' . $book->chapters()->first()->name); @@ -63,7 +63,7 @@ public function test_book_zip_endpoint() $resp = $this->get("/api/books/{$book->id}/export/zip"); $resp->assertStatus(200); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.zip"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $book->slug . '.zip'); $zip = ZipTestHelper::extractFromZipResponse($resp); $this->assertArrayHasKey('book', $zip->data); @@ -77,7 +77,7 @@ public function test_chapter_html_endpoint() $resp = $this->get("/api/chapters/{$chapter->id}/export/html"); $resp->assertStatus(200); $resp->assertSee($chapter->name); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.html"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $chapter->slug . '.html'); } public function test_chapter_plain_text_endpoint() @@ -88,7 +88,7 @@ public function test_chapter_plain_text_endpoint() $resp = $this->get("/api/chapters/{$chapter->id}/export/plaintext"); $resp->assertStatus(200); $resp->assertSee($chapter->name); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.txt"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $chapter->slug . '.txt'); } public function test_chapter_pdf_endpoint() @@ -98,7 +98,7 @@ public function test_chapter_pdf_endpoint() $resp = $this->get("/api/chapters/{$chapter->id}/export/pdf"); $resp->assertStatus(200); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $chapter->slug . '.pdf'); } public function test_chapter_markdown_endpoint() @@ -108,7 +108,7 @@ public function test_chapter_markdown_endpoint() $resp = $this->get("/api/chapters/{$chapter->id}/export/markdown"); $resp->assertStatus(200); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.md"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $chapter->slug . '.md'); $resp->assertSee('# ' . $chapter->name); $resp->assertSee('# ' . $chapter->pages()->first()->name); } @@ -120,7 +120,7 @@ public function test_chapter_zip_endpoint() $resp = $this->get("/api/chapters/{$chapter->id}/export/zip"); $resp->assertStatus(200); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.zip"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $chapter->slug . '.zip'); $zip = ZipTestHelper::extractFromZipResponse($resp); $this->assertArrayHasKey('chapter', $zip->data); @@ -134,7 +134,7 @@ public function test_page_html_endpoint() $resp = $this->get("/api/pages/{$page->id}/export/html"); $resp->assertStatus(200); $resp->assertSee($page->name); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.html"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $page->slug . '.html'); } public function test_page_plain_text_endpoint() @@ -145,7 +145,7 @@ public function test_page_plain_text_endpoint() $resp = $this->get("/api/pages/{$page->id}/export/plaintext"); $resp->assertStatus(200); $resp->assertSee($page->name); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.txt"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $page->slug . '.txt'); } public function test_page_pdf_endpoint() @@ -155,7 +155,7 @@ public function test_page_pdf_endpoint() $resp = $this->get("/api/pages/{$page->id}/export/pdf"); $resp->assertStatus(200); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.pdf"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $page->slug . '.pdf'); } public function test_page_markdown_endpoint() @@ -166,7 +166,7 @@ public function test_page_markdown_endpoint() $resp = $this->get("/api/pages/{$page->id}/export/markdown"); $resp->assertStatus(200); $resp->assertSee('# ' . $page->name); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.md"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $page->slug . '.md'); } public function test_page_zip_endpoint() @@ -176,7 +176,7 @@ public function test_page_zip_endpoint() $resp = $this->get("/api/pages/{$page->id}/export/zip"); $resp->assertStatus(200); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.zip"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $page->slug . '.zip'); $zip = ZipTestHelper::extractFromZipResponse($resp); $this->assertArrayHasKey('page', $zip->data); diff --git a/tests/Exports/HtmlExportTest.php b/tests/Exports/HtmlExportTest.php index e039fb2cc2b..f23352e0eb9 100644 --- a/tests/Exports/HtmlExportTest.php +++ b/tests/Exports/HtmlExportTest.php @@ -18,7 +18,7 @@ public function test_page_html_export() $resp = $this->get($page->getUrl('/export/html')); $resp->assertStatus(200); $resp->assertSee($page->name); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.html"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $page->slug . '.html'); } public function test_book_html_export() @@ -31,7 +31,7 @@ public function test_book_html_export() $resp->assertStatus(200); $resp->assertSee($book->name); $resp->assertSee($page->name); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.html"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $book->slug . '.html'); } public function test_book_html_export_shows_html_descriptions() @@ -58,7 +58,7 @@ public function test_chapter_html_export() $resp->assertStatus(200); $resp->assertSee($chapter->name); $resp->assertSee($page->name); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.html"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $chapter->slug . '.html'); } public function test_chapter_html_export_shows_html_descriptions() diff --git a/tests/Exports/MarkdownExportTest.php b/tests/Exports/MarkdownExportTest.php index 09928ced29e..ac1e283f34f 100644 --- a/tests/Exports/MarkdownExportTest.php +++ b/tests/Exports/MarkdownExportTest.php @@ -14,7 +14,7 @@ public function test_page_markdown_export() $resp = $this->asEditor()->get($page->getUrl('/export/markdown')); $resp->assertStatus(200); $resp->assertSee($page->name); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.md"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $page->slug . '.md'); } public function test_page_markdown_export_uses_existing_markdown_if_apparent() diff --git a/tests/Exports/PdfExportTest.php b/tests/Exports/PdfExportTest.php index e4de87d0d2f..f311f8457db 100644 --- a/tests/Exports/PdfExportTest.php +++ b/tests/Exports/PdfExportTest.php @@ -17,7 +17,7 @@ public function test_page_pdf_export() $resp = $this->get($page->getUrl('/export/pdf')); $resp->assertStatus(200); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.pdf"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $page->slug . '.pdf'); } public function test_book_pdf_export() @@ -28,7 +28,7 @@ public function test_book_pdf_export() $resp = $this->get($book->getUrl('/export/pdf')); $resp->assertStatus(200); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.pdf"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $book->slug . '.pdf'); } public function test_chapter_pdf_export() @@ -38,7 +38,7 @@ public function test_chapter_pdf_export() $resp = $this->get($chapter->getUrl('/export/pdf')); $resp->assertStatus(200); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $chapter->slug . '.pdf'); } diff --git a/tests/Exports/TextExportTest.php b/tests/Exports/TextExportTest.php index c593a6585cb..4b2d6288775 100644 --- a/tests/Exports/TextExportTest.php +++ b/tests/Exports/TextExportTest.php @@ -14,7 +14,7 @@ public function test_page_text_export() $resp = $this->get($page->getUrl('/export/plaintext')); $resp->assertStatus(200); $resp->assertSee($page->name); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.txt"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $page->slug . '.txt'); } public function test_book_text_export() @@ -35,7 +35,7 @@ public function test_book_text_export() $resp->assertSee($directPage->name); $resp->assertSee('My awesome page'); $resp->assertSee('My little nested page'); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.txt"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $book->slug . '.txt'); } public function test_book_text_export_format() @@ -68,7 +68,7 @@ public function test_chapter_text_export() $resp->assertSee($chapter->name); $resp->assertSee($page->name); $resp->assertSee('This is content within the page!'); - $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.txt"'); + $resp->assertHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . $chapter->slug . '.txt'); } public function test_chapter_text_export_format() diff --git a/tests/Uploads/AttachmentTest.php b/tests/Uploads/AttachmentTest.php index b443ca9f117..945fc258d77 100644 --- a/tests/Uploads/AttachmentTest.php +++ b/tests/Uploads/AttachmentTest.php @@ -324,7 +324,7 @@ public function test_file_access_with_open_query_param_provides_inline_response_ $attachmentGet = $this->get($attachment->getUrl(true)); // http-foundation/Response does some 'fixing' of responses to add charsets to text responses. $attachmentGet->assertHeader('Content-Type', 'text/plain; charset=utf-8'); - $attachmentGet->assertHeader('Content-Disposition', 'inline; filename="upload_test_file.txt"'); + $attachmentGet->assertHeader('Content-Disposition', 'inline; filename*=UTF-8\'\'upload_test_file.txt'); $attachmentGet->assertHeader('X-Content-Type-Options', 'nosniff'); $this->files->deleteAllAttachmentFiles(); @@ -340,7 +340,22 @@ public function test_html_file_access_with_open_forces_plain_content_type() $attachmentGet = $this->get($attachment->getUrl(true)); // http-foundation/Response does some 'fixing' of responses to add charsets to text responses. $attachmentGet->assertHeader('Content-Type', 'text/plain; charset=utf-8'); - $attachmentGet->assertHeader('Content-Disposition', 'inline; filename="test_file.html"'); + $attachmentGet->assertHeader('Content-Disposition', 'inline; filename*=UTF-8\'\'test_file.html'); + + $this->files->deleteAllAttachmentFiles(); + } + + public function test_file_access_name_in_content_disposition_header_is_sanitized() + { + $page = $this->entities->page(); + $this->asAdmin(); + + $attachment = $this->files->uploadAttachmentDataToPage($this, $page, 'test_file.html', '

    testing

    ', 'text/html'); + $attachment->name = "my\\_/super\n_fu\$n_\tfile"; + $attachment->save(); + + $attachmentGet = $this->get($attachment->getUrl(true)); + $attachmentGet->assertHeader('Content-Disposition', 'inline; filename*=UTF-8\'\'my_super_fun_file.html'); $this->files->deleteAllAttachmentFiles(); } From 0120b475eb18c7d4e1851a937cafd12724fabb31 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 17 Mar 2026 10:59:11 +0000 Subject: [PATCH 086/204] Deps: Updated PHP deps pre v26.03.1 --- composer.lock | 58 +++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/composer.lock b/composer.lock index 030cd1a7397..e1e56ff9db6 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.373.2", + "version": "3.373.3", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "483fba51c28b3a0c0647bf5100e0edca82090b18" + "reference": "d23edc4cf9cd81cb98b5beb9c1fb3737f535b1e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/483fba51c28b3a0c0647bf5100e0edca82090b18", - "reference": "483fba51c28b3a0c0647bf5100e0edca82090b18", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d23edc4cf9cd81cb98b5beb9c1fb3737f535b1e5", + "reference": "d23edc4cf9cd81cb98b5beb9c1fb3737f535b1e5", "shasum": "" }, "require": { @@ -153,22 +153,22 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.373.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.373.3" }, - "time": "2026-03-13T18:08:30+00:00" + "time": "2026-03-16T18:15:27+00:00" }, { "name": "bacon/bacon-qr-code", - "version": "v3.0.3", + "version": "v3.0.4", "source": { "type": "git", "url": "https://github.com/Bacon/BaconQrCode.git", - "reference": "36a1cb2b81493fa5b82e50bf8068bf84d1542563" + "reference": "3feed0e212b8412cc5d2612706744789b0615824" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/36a1cb2b81493fa5b82e50bf8068bf84d1542563", - "reference": "36a1cb2b81493fa5b82e50bf8068bf84d1542563", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/3feed0e212b8412cc5d2612706744789b0615824", + "reference": "3feed0e212b8412cc5d2612706744789b0615824", "shasum": "" }, "require": { @@ -208,9 +208,9 @@ "homepage": "https://github.com/Bacon/BaconQrCode", "support": { "issues": "https://github.com/Bacon/BaconQrCode/issues", - "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.3" + "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.4" }, - "time": "2025-11-19T17:15:36+00:00" + "time": "2026-03-16T01:01:30+00:00" }, { "name": "brick/math", @@ -2943,20 +2943,20 @@ }, { "name": "league/uri", - "version": "7.8.0", + "version": "7.8.1", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "4436c6ec8d458e4244448b069cc572d088230b76" + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/4436c6ec8d458e4244448b069cc572d088230b76", - "reference": "4436c6ec8d458e4244448b069cc572d088230b76", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/08cf38e3924d4f56238125547b5720496fac8fd4", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.8", + "league/uri-interfaces": "^7.8.1", "php": "^8.1", "psr/http-factory": "^1" }, @@ -3029,7 +3029,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.8.0" + "source": "https://github.com/thephpleague/uri/tree/7.8.1" }, "funding": [ { @@ -3037,20 +3037,20 @@ "type": "github" } ], - "time": "2026-01-14T17:24:56+00:00" + "time": "2026-03-15T20:22:25+00:00" }, { "name": "league/uri-interfaces", - "version": "7.8.0", + "version": "7.8.1", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4" + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/c5c5cd056110fc8afaba29fa6b72a43ced42acd4", - "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/85d5c77c5d6d3af6c54db4a78246364908f3c928", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928", "shasum": "" }, "require": { @@ -3113,7 +3113,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.1" }, "funding": [ { @@ -3121,7 +3121,7 @@ "type": "github" } ], - "time": "2026-01-15T06:54:53+00:00" + "time": "2026-03-08T20:05:35+00:00" }, { "name": "masterminds/html5", @@ -9169,11 +9169,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.40", + "version": "2.1.41", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", - "reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a2eae8f20856b3afe74bf1f9726ce8c11438e300", + "reference": "a2eae8f20856b3afe74bf1f9726ce8c11438e300", "shasum": "" }, "require": { @@ -9218,7 +9218,7 @@ "type": "github" } ], - "time": "2026-02-23T15:04:35+00:00" + "time": "2026-03-16T18:24:10+00:00" }, { "name": "phpunit/php-code-coverage", From 04dd9f8e19301fde58218864ae51eef6b678f27a Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 17 Mar 2026 17:21:01 -0300 Subject: [PATCH 087/204] Update PHP_CodeSniffer repository link --- dev/docs/development.md | 2 +- readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/docs/development.md b/dev/docs/development.md index 418e9ead672..2c73a0256c5 100644 --- a/dev/docs/development.md +++ b/dev/docs/development.md @@ -37,7 +37,7 @@ We use tools to manage code standards and formatting within the project. If subm ### PHP -PHP code standards are managed by [using PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer). +PHP code standards are managed by [using PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer). Static analysis is in place using [PHPStan](https://phpstan.org/) & [Larastan](https://github.com/nunomaduro/larastan). The below commands can be used to utilise these tools: diff --git a/readme.md b/readme.md index 194f81e4a05..00eb135046e 100644 --- a/readme.md +++ b/readme.md @@ -178,7 +178,7 @@ Note: This is not an exhaustive list of all libraries and projects that would be * [phpseclib](https://github.com/phpseclib/phpseclib) - _[MIT](https://github.com/phpseclib/phpseclib/blob/master/LICENSE)_ * [Clockwork](https://github.com/itsgoingd/clockwork) - _[MIT](https://github.com/itsgoingd/clockwork/blob/master/LICENSE)_ * [PHPStan](https://phpstan.org/) & [Larastan](https://github.com/nunomaduro/larastan) - _[MIT](https://github.com/phpstan/phpstan/blob/master/LICENSE) and [MIT](https://github.com/nunomaduro/larastan/blob/master/LICENSE.md)_ -* [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) - _[BSD 3-Clause](https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt)_ +* [PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer) - _[BSD 3-Clause](https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt)_ * [JakeArchibald/IDB-Keyval](https://github.com/jakearchibald/idb-keyval) - _[Apache-2.0](https://github.com/jakearchibald/idb-keyval/blob/main/LICENCE)_ * [HTML Purifier](https://github.com/ezyang/htmlpurifier) and [htmlpurifier-html5](https://github.com/xemlock/htmlpurifier-html5) - _[LGPL-2.1](https://github.com/ezyang/htmlpurifier/blob/master/LICENSE) and [MIT](https://github.com/xemlock/htmlpurifier-html5/blob/master/LICENSE)_ From 5763d26b175769675f78ce5955601313ac772a15 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 19 Mar 2026 21:29:30 +0000 Subject: [PATCH 088/204] Updated registration to use validated input instead of all --- app/Access/Controllers/RegisterController.php | 3 +-- app/Access/RegistrationService.php | 6 ++--- tests/Auth/RegistrationTest.php | 24 +++++++++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/app/Access/Controllers/RegisterController.php b/app/Access/Controllers/RegisterController.php index e9812aa5d06..f0261fba80d 100644 --- a/app/Access/Controllers/RegisterController.php +++ b/app/Access/Controllers/RegisterController.php @@ -48,8 +48,7 @@ public function getRegister() public function postRegister(Request $request) { $this->registrationService->ensureRegistrationAllowed(); - $this->validator($request->all())->validate(); - $userData = $request->all(); + $userData = $this->validator($request->all())->validate(); try { $user = $this->registrationService->registerUser($userData); diff --git a/app/Access/RegistrationService.php b/app/Access/RegistrationService.php index 68992fbc65c..e47479e7991 100644 --- a/app/Access/RegistrationService.php +++ b/app/Access/RegistrationService.php @@ -83,7 +83,7 @@ public function registerUser(array $userData, ?SocialAccount $socialAccount = nu // Email restriction $this->ensureEmailDomainAllowed($userEmail); - // Ensure user does not already exist + // Ensure the user does not already exist $alreadyUser = !is_null($this->userRepo->getByEmail($userEmail)); if ($alreadyUser) { throw new UserRegistrationException(trans('errors.error_user_exists_different_creds', ['email' => $userEmail]), '/login'); @@ -99,7 +99,7 @@ public function registerUser(array $userData, ?SocialAccount $socialAccount = nu $newUser = $this->userRepo->createWithoutActivity($userData, $emailConfirmed); $newUser->attachDefaultRole(); - // Assign social account if given + // Assign a social account if given if ($socialAccount) { $newUser->socialAccounts()->save($socialAccount); } @@ -107,7 +107,7 @@ public function registerUser(array $userData, ?SocialAccount $socialAccount = nu Activity::add(ActivityType::AUTH_REGISTER, $socialAccount ?? $newUser); Theme::dispatch(ThemeEvents::AUTH_REGISTER, $authSystem, $newUser); - // Start email confirmation flow if required + // Start the email confirmation flow if required if ($this->emailConfirmationService->confirmationRequired() && !$emailConfirmed) { $newUser->save(); diff --git a/tests/Auth/RegistrationTest.php b/tests/Auth/RegistrationTest.php index 2666fa3b4c7..e0d7c262682 100644 --- a/tests/Auth/RegistrationTest.php +++ b/tests/Auth/RegistrationTest.php @@ -188,6 +188,30 @@ public function test_registration_validation() $resp->assertSee('The password must be at least 8 characters.'); } + public function test_registration_input_filtered_to_validated_input() + { + $this->setSettings(['registration-enabled' => 'true']); + $roleIds = Role::all()->pluck('id')->toArray(); + + $resp = $this->post('/register', [ + 'name' => 'Barry', + 'email' => 'barry@example.com', + 'password' => 'superpassword', + 'password_confirmation' => 'superpassword', + 'external_auth_id' => 'ext5691284', + 'roles' => $roleIds, + ]); + + $resp->assertRedirect('/'); + + /** @var User $user */ + $user = auth()->user(); + $this->assertNotNull($user); + $this->assertFalse($user->isGuest()); + $this->assertEmpty($user->external_auth_id); + $this->assertEquals(0, $user->roles()->count()); + } + public function test_registration_simple_honeypot_active() { $this->setSettings(['registration-enabled' => 'true']); From a44756168dcbce0813aaefa073f9dc7e59420636 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 22 Mar 2026 17:18:04 +0000 Subject: [PATCH 089/204] WYSIWYG: Aligned double click to set label for details functionality Aligned the behaviour across the WYSIWYG editors, and also for nested details blocks (which wasn't working in the TinyMCE implementation). Closes #6059 --- .../js/wysiwyg-tinymce/plugins-details.js | 10 ++++++++-- .../js/wysiwyg/lexical/core/LexicalEditor.ts | 19 +++++++++++++++++++ .../lexical/rich-text/LexicalDetailsNode.ts | 11 +++++++++++ .../js/wysiwyg/ui/defaults/buttons/objects.ts | 2 +- resources/js/wysiwyg/ui/framework/manager.ts | 7 +++++-- 5 files changed, 44 insertions(+), 5 deletions(-) diff --git a/resources/js/wysiwyg-tinymce/plugins-details.js b/resources/js/wysiwyg-tinymce/plugins-details.js index c4a6d927d2b..58b0ac9d869 100644 --- a/resources/js/wysiwyg-tinymce/plugins-details.js +++ b/resources/js/wysiwyg-tinymce/plugins-details.js @@ -19,6 +19,8 @@ function setSummary(editor, summaryContent) { } summary.textContent = summaryContent; }); + + editor.selection.select(details); } /** @@ -202,8 +204,12 @@ function register(editor) { }); editor.on('dblclick', event => { - if (!getSelectedDetailsBlock(editor) || event.target.closest('doc-root')) return; - showDetailLabelEditWindow(editor); + const domElClass = event?.target?.ownerDocument?.defaultView?.HTMLDetailsElement; + if (domElClass && event.target instanceof domElClass && getSelectedDetailsBlock(editor)) { + showDetailLabelEditWindow(editor); + event.preventDefault(); + event.stopPropagation(); + } }); editor.ui.registry.addButton('toggledetails', { diff --git a/resources/js/wysiwyg/lexical/core/LexicalEditor.ts b/resources/js/wysiwyg/lexical/core/LexicalEditor.ts index 364f6c6b7c3..46660c9b74a 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalEditor.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalEditor.ts @@ -45,6 +45,7 @@ import {LineBreakNode} from './nodes/LexicalLineBreakNode'; import {ParagraphNode} from './nodes/LexicalParagraphNode'; import {RootNode} from './nodes/LexicalRootNode'; import {TabNode} from './nodes/LexicalTabNode'; +import {EditorUiContext} from "../../ui/framework/core"; export type Spread = Omit & T1; @@ -621,6 +622,8 @@ export class LexicalEditor { _editable: boolean; /** @internal */ _blockCursorElement: null | HTMLDivElement; + /** @internal */ + _context: null | EditorUiContext; /** @internal */ constructor( @@ -682,6 +685,7 @@ export class LexicalEditor { this._headless = parentEditor !== null && parentEditor._headless; this._window = null; this._blockCursorElement = null; + this._context = null; } /** @@ -1285,6 +1289,21 @@ export class LexicalEditor { triggerListeners('editable', this, true, editable); } } + + /** + * Set the UI context that this editor is intended to be part of. + */ + setUiContext(context: EditorUiContext) { + this._context = context; + } + + /** + * Get the UI context that this editor is considered to be part of. + */ + getUiContext(): EditorUiContext|null { + return this._context; + } + /** * Returns a JSON-serializable javascript object NOT a JSON string. * You still must call JSON.stringify (or something else) to turn the diff --git a/resources/js/wysiwyg/lexical/rich-text/LexicalDetailsNode.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalDetailsNode.ts index cdf32fdcbb1..70ae7f0f9f6 100644 --- a/resources/js/wysiwyg/lexical/rich-text/LexicalDetailsNode.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalDetailsNode.ts @@ -9,6 +9,7 @@ import { } from 'lexical'; import {extractDirectionFromElement} from "lexical/nodes/common"; +import {$showDetailsForm} from "../../ui/defaults/forms/objects"; export type SerializedDetailsNode = Spread<{ id: string; @@ -90,6 +91,16 @@ export class DetailsNode extends ElementNode { }); }); + summary.addEventListener('dblclick', event => { + event.preventDefault(); + const uiContext = _editor.getUiContext(); + if (uiContext) { + _editor.read(() => { + $showDetailsForm(this, uiContext); + }); + } + }); + el.append(summary); return el; diff --git a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts index 00dc9500ec8..bd6e41da918 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts @@ -221,7 +221,7 @@ export const detailsEditLabel: EditorButtonDefinition = { if ($isDetailsNode(details)) { $showDetailsForm(details, context); } - }) + }); }, isActive(selection: BaseSelection | null): boolean { return false; diff --git a/resources/js/wysiwyg/ui/framework/manager.ts b/resources/js/wysiwyg/ui/framework/manager.ts index 78d0cc9a27a..3b4d5b495a8 100644 --- a/resources/js/wysiwyg/ui/framework/manager.ts +++ b/resources/js/wysiwyg/ui/framework/manager.ts @@ -29,7 +29,7 @@ export class EditorUIManager { setContext(context: EditorUiContext) { this.context = context; this.setupEventListeners(); - this.setupEditor(context.editor); + this.setupEditor(context.editor, context); } getContext(): EditorUiContext { @@ -256,7 +256,10 @@ export class EditorUIManager { } } - protected setupEditor(editor: LexicalEditor) { + protected setupEditor(editor: LexicalEditor, context: EditorUiContext) { + // Pass the context to the editor + editor.setUiContext(context); + // Register our DOM decorate listener with the editor const domDecorateListener: DecoratorListener = (decorators: Record) => { editor.getEditorState().read(() => { From 5ebc1fe3b0a071b93148f16703c6d96c4af25ccf Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 22 Mar 2026 17:22:13 +0000 Subject: [PATCH 090/204] Deps: Updated PHP package versions pre v26.03.2 release --- composer.lock | 80 +++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/composer.lock b/composer.lock index e1e56ff9db6..d6069720d29 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.373.3", + "version": "3.373.7", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "d23edc4cf9cd81cb98b5beb9c1fb3737f535b1e5" + "reference": "4402bd10f913e66b7271f44466be8d5ba6c9146e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d23edc4cf9cd81cb98b5beb9c1fb3737f535b1e5", - "reference": "d23edc4cf9cd81cb98b5beb9c1fb3737f535b1e5", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/4402bd10f913e66b7271f44466be8d5ba6c9146e", + "reference": "4402bd10f913e66b7271f44466be8d5ba6c9146e", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.373.3" + "source": "https://github.com/aws/aws-sdk-php/tree/3.373.7" }, - "time": "2026-03-16T18:15:27+00:00" + "time": "2026-03-20T18:14:19+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1801,16 +1801,16 @@ }, { "name": "laravel/framework", - "version": "v12.54.1", + "version": "v12.55.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "325497463e7599cd14224c422c6e5dd2fe832868" + "reference": "6d9185a248d101b07eecaf8fd60b18129545fd33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/325497463e7599cd14224c422c6e5dd2fe832868", - "reference": "325497463e7599cd14224c422c6e5dd2fe832868", + "url": "https://api.github.com/repos/laravel/framework/zipball/6d9185a248d101b07eecaf8fd60b18129545fd33", + "reference": "6d9185a248d101b07eecaf8fd60b18129545fd33", "shasum": "" }, "require": { @@ -1926,7 +1926,7 @@ "orchestra/testbench-core": "^10.9.0", "pda/pheanstalk": "^5.0.6|^7.0.0", "php-http/discovery": "^1.15", - "phpstan/phpstan": "^2.0", + "phpstan/phpstan": "^2.1.41", "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", "predis/predis": "^2.3|^3.0", "resend/resend-php": "^0.10.0|^1.0", @@ -2019,20 +2019,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-03-10T20:25:56+00:00" + "time": "2026-03-18T14:28:59+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.14", + "version": "v0.3.15", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176" + "reference": "4bb8107ec97651fd3f17f897d6489dbc4d8fb999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", - "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", + "url": "https://api.github.com/repos/laravel/prompts/zipball/4bb8107ec97651fd3f17f897d6489dbc4d8fb999", + "reference": "4bb8107ec97651fd3f17f897d6489dbc4d8fb999", "shasum": "" }, "require": { @@ -2076,9 +2076,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.14" + "source": "https://github.com/laravel/prompts/tree/v0.3.15" }, - "time": "2026-03-01T09:02:38+00:00" + "time": "2026-03-17T13:45:17+00:00" }, { "name": "laravel/serializable-closure", @@ -2281,16 +2281,16 @@ }, { "name": "league/commonmark", - "version": "2.8.1", + "version": "2.8.2", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "84b1ca48347efdbe775426f108622a42735a6579" + "reference": "59fb075d2101740c337c7216e3f32b36c204218b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/84b1ca48347efdbe775426f108622a42735a6579", - "reference": "84b1ca48347efdbe775426f108622a42735a6579", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/59fb075d2101740c337c7216e3f32b36c204218b", + "reference": "59fb075d2101740c337c7216e3f32b36c204218b", "shasum": "" }, "require": { @@ -2384,7 +2384,7 @@ "type": "tidelift" } ], - "time": "2026-03-05T21:37:03+00:00" + "time": "2026-03-19T13:16:38+00:00" }, { "name": "league/config", @@ -4027,16 +4027,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.49", + "version": "3.0.50", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9" + "reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/6233a1e12584754e6b5daa69fe1289b47775c1b9", - "reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/aa6ad8321ed103dc3624fb600a25b66ebf78ec7b", + "reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b", "shasum": "" }, "require": { @@ -4117,7 +4117,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.49" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.50" }, "funding": [ { @@ -4133,7 +4133,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T09:17:28+00:00" + "time": "2026-03-19T02:57:58+00:00" }, { "name": "pragmarx/google2fa", @@ -5154,22 +5154,22 @@ }, { "name": "socialiteproviders/manager", - "version": "v4.8.1", + "version": "4.9.2", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Manager.git", - "reference": "8180ec14bef230ec2351cff993d5d2d7ca470ef4" + "reference": "35372dc62787e61e91cfec73f45fd5d5ae0f8891" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/8180ec14bef230ec2351cff993d5d2d7ca470ef4", - "reference": "8180ec14bef230ec2351cff993d5d2d7ca470ef4", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/35372dc62787e61e91cfec73f45fd5d5ae0f8891", + "reference": "35372dc62787e61e91cfec73f45fd5d5ae0f8891", "shasum": "" }, "require": { - "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", + "illuminate/support": "^11.0 || ^12.0 || ^13.0", "laravel/socialite": "^5.5", - "php": "^8.1" + "php": "^8.2" }, "require-dev": { "mockery/mockery": "^1.2", @@ -5224,7 +5224,7 @@ "issues": "https://github.com/socialiteproviders/manager/issues", "source": "https://github.com/socialiteproviders/manager" }, - "time": "2025-02-24T19:33:30+00:00" + "time": "2026-03-18T22:13:24+00:00" }, { "name": "socialiteproviders/microsoft-azure", @@ -9169,11 +9169,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.41", + "version": "2.1.42", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a2eae8f20856b3afe74bf1f9726ce8c11438e300", - "reference": "a2eae8f20856b3afe74bf1f9726ce8c11438e300", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1279e1ce86ba768f0780c9d889852b4e02ff40d0", + "reference": "1279e1ce86ba768f0780c9d889852b4e02ff40d0", "shasum": "" }, "require": { @@ -9218,7 +9218,7 @@ "type": "github" } ], - "time": "2026-03-16T18:24:10+00:00" + "time": "2026-03-17T14:58:32+00:00" }, { "name": "phpunit/php-code-coverage", From fd6867e577f271289ac6ba1490af2aef784996e8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 23 Mar 2026 10:05:51 +0000 Subject: [PATCH 091/204] Updated translations with latest Crowdin changes (#6064) --- lang/cs/settings.php | 2 +- lang/de/errors.php | 4 ++-- lang/de/notifications.php | 4 ++-- lang/de/preferences.php | 2 +- lang/de_informal/errors.php | 4 ++-- lang/de_informal/notifications.php | 4 ++-- lang/de_informal/preferences.php | 2 +- lang/fr/settings.php | 2 +- lang/ja/settings.php | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lang/cs/settings.php b/lang/cs/settings.php index ef25f1a2035..a8c4036e87e 100644 --- a/lang/cs/settings.php +++ b/lang/cs/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Kapitoly jako první', 'sort_rule_op_chapters_last' => 'Kapitoly jako poslední', 'sorting_page_limits' => 'Počet zobrazených položek na stránce', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sorting_page_limits_desc' => 'Nastavte, kolik položek se má zobrazit na stránce v různých seznamech na webu. Obvykle bude nižší počet výkonnější, zatímco vyšší počet eliminuje nutnost proklikávat se několika stránkami. Doporučuje se použít násobek čísla 6.', // Maintenance settings 'maint' => 'Údržba', diff --git a/lang/de/errors.php b/lang/de/errors.php index 75c1cf3944e..ee5071f8578 100644 --- a/lang/de/errors.php +++ b/lang/de/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => 'ZIP-Datei konnte nicht gelesen werden.', 'import_zip_cant_decode_data' => 'ZIP data.json konnte nicht gefunden und dekodiert werden.', 'import_zip_no_data' => 'ZIP-Datei Daten haben kein erwartetes Buch, Kapitel oder Seiteninhalt.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'Der Inhalt der ZIP data.json überschreitet die maximale Dateigröße der Anwendung.', 'import_validation_failed' => 'ZIP Import konnte mit Fehlern nicht validiert werden:', 'import_zip_failed_notification' => 'Importieren der ZIP-Datei fehlgeschlagen.', 'import_perms_books' => 'Ihnen fehlt die erforderliche Berechtigung, um Bücher zu erstellen.', @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => 'Das Kennwort für das angegebene API-Token ist falsch', 'api_user_no_api_permission' => 'Der Besitzer des verwendeten API-Tokens hat keine Berechtigung für API-Aufrufe', 'api_user_token_expired' => 'Das verwendete Autorisierungstoken ist abgelaufen', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Nur GET Anfragen sind erlaubt, wenn die API mit Cookie-basierter Authentifizierung verwendet wird', // Settings & Maintenance 'maintenance_test_email_failure' => 'Fehler beim Versenden einer Test E-Mail:', diff --git a/lang/de/notifications.php b/lang/de/notifications.php index 71b71d1b662..cd8f4c87418 100644 --- a/lang/de/notifications.php +++ b/lang/de/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => 'Aktualisierte Seite: :pageName', 'updated_page_intro' => 'Eine Seite wurde in :appName aktualisiert:', 'updated_page_debounce' => 'Um eine Flut von Benachrichtigungen zu vermeiden, werden Sie für eine gewisse Zeit keine Benachrichtigungen für weitere Bearbeitungen dieser Seite durch denselben Bearbeiter erhalten.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'Sie wurden in einem Kommentar auf der Seite :pageName erwähnt', + 'comment_mention_intro' => 'Sie wurden in einem Kommentar zu :appName: erwähnt', 'detail_page_name' => 'Name der Seite:', 'detail_page_path' => 'Seitenpfad:', diff --git a/lang/de/preferences.php b/lang/de/preferences.php index 1f74f3d3eb0..858cf35a38c 100644 --- a/lang/de/preferences.php +++ b/lang/de/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'Legen Sie fest, welche E-Mail-Benachrichtigungen Sie erhalten, wenn bestimmte Aktivitäten im System durchgeführt werden.', 'notifications_opt_own_page_changes' => 'Benachrichtigung bei Änderungen an eigenen Seiten', 'notifications_opt_own_page_comments' => 'Benachrichtigung bei Kommentaren an eigenen Seiten', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'Bei Erwähnung mich benachrichtigen', 'notifications_opt_comment_replies' => 'Bei Antworten auf meine Kommentare benachrichtigen', 'notifications_save' => 'Einstellungen speichern', 'notifications_update_success' => 'Benachrichtigungseinstellungen wurden aktualisiert!', diff --git a/lang/de_informal/errors.php b/lang/de_informal/errors.php index a3be19a028d..f43b4e5acf6 100644 --- a/lang/de_informal/errors.php +++ b/lang/de_informal/errors.php @@ -109,7 +109,7 @@ 'import_zip_cant_read' => 'ZIP-Datei konnte nicht gelesen werden.', 'import_zip_cant_decode_data' => 'Konnte Inhalt der data.json im ZIP nicht finden und dekodieren.', 'import_zip_no_data' => 'ZIP-Datei hat kein erwartetes Buch, Kapitel oder Seiteninhalt.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_data_too_large' => 'Der Inhalt der ZIP data.json überschreitet die maximale Dateigröße der Anwendung.', 'import_validation_failed' => 'ZIP Import konnte aufgrund folgender Fehler nicht validiert werden:', 'import_zip_failed_notification' => 'Importieren der ZIP-Datei fehlgeschlagen.', 'import_perms_books' => 'Dir fehlt die erforderliche Berechtigung, um Bücher zu erstellen.', @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => 'Das für den API-Token angegebene geheime Token ist falsch', 'api_user_no_api_permission' => 'Der Besitzer des verwendeten API-Token hat keine Berechtigung für API-Aufrufe', 'api_user_token_expired' => 'Das verwendete Autorisierungs-Token ist abgelaufen', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Nur GET Anfragen sind erlaubt, wenn die API mit Cookie-basierter Authentifizierung verwendet wird', // Settings & Maintenance 'maintenance_test_email_failure' => 'Fehler beim Senden einer Test E-Mail:', diff --git a/lang/de_informal/notifications.php b/lang/de_informal/notifications.php index 99c270ec1be..4df17078320 100644 --- a/lang/de_informal/notifications.php +++ b/lang/de_informal/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => 'Aktualisierte Seite: :pageName', 'updated_page_intro' => 'Eine Seite wurde in :appName aktualisiert:', 'updated_page_debounce' => 'Um eine Flut von Benachrichtigungen zu vermeiden, wirst du für eine gewisse Zeit keine Benachrichtigungen für weitere Bearbeitungen dieser Seite durch denselben Bearbeiter erhalten.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'Sie wurden in einem Kommentar auf der Seite :pageName erwähnt', + 'comment_mention_intro' => 'Sie wurden in einem Kommentar zu :appName: erwähnt', 'detail_page_name' => 'Seitenname:', 'detail_page_path' => 'Seitenpfad:', diff --git a/lang/de_informal/preferences.php b/lang/de_informal/preferences.php index bfb57b2f492..443f70b0a87 100644 --- a/lang/de_informal/preferences.php +++ b/lang/de_informal/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'Lege fest, welche E-Mail-Benachrichtigungen du erhältst, wenn bestimmte Aktivitäten im System durchgeführt werden.', 'notifications_opt_own_page_changes' => 'Benachrichtigung bei Änderungen an eigenen Seiten', 'notifications_opt_own_page_comments' => 'Benachrichtigung bei Kommentaren an eigenen Seiten', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'Bei Erwähnung mich benachrichtigen', 'notifications_opt_comment_replies' => 'Bei Antworten auf meine Kommentare benachrichtigen', 'notifications_save' => 'Einstellungen speichern', 'notifications_update_success' => 'Benachrichtigungseinstellungen wurden aktualisiert!', diff --git a/lang/fr/settings.php b/lang/fr/settings.php index 7b0987a488c..8c6c57f33aa 100644 --- a/lang/fr/settings.php +++ b/lang/fr/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Chapitres en premier', 'sort_rule_op_chapters_last' => 'Chapitres en dernier', 'sorting_page_limits' => 'Limite d\'affichage par page', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sorting_page_limits_desc' => 'Définissez le nombre d’éléments à afficher par page dans les différentes listes du système. En général, un nombre plus faible offre de meilleures performances, tandis qu’un nombre plus élevé réduit le besoin de naviguer entre plusieurs pages. Il est recommandé d’utiliser un multiple de 6.', // Maintenance settings 'maint' => 'Maintenance', diff --git a/lang/ja/settings.php b/lang/ja/settings.php index 717a4c10ffc..378e3a7748e 100644 --- a/lang/ja/settings.php +++ b/lang/ja/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'チャプタを最初に', 'sort_rule_op_chapters_last' => 'チャプタを最後に', 'sorting_page_limits' => 'ページング表示制限', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sorting_page_limits_desc' => 'システム内の各種リストで1ページに表示するアイテム数を設定します。 通常、少ない数に設定するとパフォーマンスが向上し、多い数に設定するとページの移動操作が少なくなります。6 の倍数に設定することをお勧めします。', // Maintenance settings 'maint' => 'メンテナンス', From 1763ac550b3dbc7339cbdbb9791253bcddbaf67a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 23 Mar 2026 10:08:38 +0000 Subject: [PATCH 092/204] Meta: Updated translators pre v26.03.2 release --- .github/translators.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/translators.txt b/.github/translators.txt index fcbb9675dda..97ab6c3fc6f 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -533,3 +533,4 @@ JanDziaslo :: Polish Charllys Fernandes (CharllysFernandes) :: Portuguese, Brazilian Ilgiz Zigangirov (inov8) :: Russian Max Israelsson (Blezie) :: Swedish +Skiddybison5924 (chris-devel0per) :: German From 0b659671fe08da3a699f0b4ddd93e77e18d13b11 Mon Sep 17 00:00:00 2001 From: ololukaszuk <47779810+ololukaszuk@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:23:15 +0100 Subject: [PATCH 093/204] Fix PDF heading font fallback for export --- resources/sass/export-styles.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/sass/export-styles.scss b/resources/sass/export-styles.scss index 8dd7be375e8..22f15d1b82a 100644 --- a/resources/sass/export-styles.scss +++ b/resources/sass/export-styles.scss @@ -64,6 +64,11 @@ body.export-format-pdf { font-size: 14px; line-height: 1.2; + // Ensure heading glyph coverage for PDF engines that don't handle CSS vars well. + h1, h2, h3, h4, h5, h6 { + font-family: 'DejaVu Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Roboto", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + } + h1, h2, h3, h4, h5, h6 { line-height: 1.2; } @@ -100,4 +105,4 @@ body.export-format-pdf.export-engine-dompdf { .page-content td a > img { max-width: 100%; } -} \ No newline at end of file +} From e42fda893c62286d2948b0f18f4dd2fd846de098 Mon Sep 17 00:00:00 2001 From: Zhey Date: Thu, 26 Mar 2026 12:00:08 +0100 Subject: [PATCH 094/204] Add CSP controls for image and CSS sources --- .env.example.complete | 12 +++++++ app/Config/app.php | 10 ++++++ app/Util/CspService.php | 65 ++++++++++++++++++++++++++++++++++++ dev/docs/development.md | 42 +++++++++++++++++++++++ readme.md | 1 + tests/SecurityHeaderTest.php | 36 ++++++++++++++++++++ 6 files changed, 166 insertions(+) diff --git a/.env.example.complete b/.env.example.complete index ebebaf9e3e8..6a7f9db6529 100644 --- a/.env.example.complete +++ b/.env.example.complete @@ -395,6 +395,18 @@ 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_CSS_SOURCES="https://fonts.googleapis.com" +ALLOWED_CSS_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. +# Defaults to a permissive set if not provided. +# Example: ALLOWED_IMAGE_SOURCES="https://images.example.com data:" +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 diff --git a/app/Config/app.php b/app/Config/app.php index a476fdfea1c..5536e9abdd4 100644 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -72,6 +72,16 @@ // 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 sources/hostnames that can be loaded as CSS 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. + 'css_sources' => env('ALLOWED_CSS_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. + // 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 diff --git a/app/Util/CspService.php b/app/Util/CspService.php index 466acb49148..a0e1faadf95 100644 --- a/app/Util/CspService.php +++ b/app/Util/CspService.php @@ -30,6 +30,8 @@ public function getCspHeader(): string $this->getFrameAncestors(), $this->getFrameSrc(), $this->getScriptSrc(), + $this->getStyleSrc(), + $this->getImgSrc(), $this->getObjectSrc(), $this->getBaseUri(), ]; @@ -45,6 +47,8 @@ public function getCspMetaTagValue(): string $headers = [ $this->getFrameSrc(), $this->getScriptSrc(), + $this->getStyleSrc(), + $this->getImgSrc(), $this->getObjectSrc(), $this->getBaseUri(), ]; @@ -115,6 +119,22 @@ protected function getObjectSrc(): string 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. @@ -144,6 +164,51 @@ protected function getAllowedIframeSources(): array return array_filter($sources); } + /** + * Get allowed style sources for the style-src directive. + */ + protected function getAllowedStyleSources(): array + { + $configured = config('app.css_sources'); + + if (is_string($configured)) { + $sources = array_filter(explode(' ', $configured)); + array_unshift($sources, "'self'"); + + 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'"); + + 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. diff --git a/dev/docs/development.md b/dev/docs/development.md index 2c73a0256c5..16f168f9cda 100644 --- a/dev/docs/development.md +++ b/dev/docs/development.md @@ -31,6 +31,48 @@ BookStack has a large suite of PHP tests to cover application functionality. We For details about setting-up, running and writing tests please see the [php-testing.md document](php-testing.md). +## Content Security Policy Controls + +BookStack enforces a Content Security Policy (CSP) response header to reduce risk from injected content and untrusted embeds. + +For backward compatibility, image and CSS controls are intentionally permissive by default, but can be tightened via environment options. + +### Related Environment Options + +These values are defined in `.env.example.complete`: + +- `ALLOWED_CSS_SOURCES` + - Controls allowed `style-src` sources. + - Defaults to a permissive fallback if unset. +- `ALLOWED_IMAGE_SOURCES` + - Controls allowed `img-src` sources. + - Defaults to a permissive fallback if unset. + +Values should be space-separated source expressions. + +### Example Configurations + +Allow Google Fonts CSS and local styles only: + +```bash +ALLOWED_CSS_SOURCES="https://fonts.googleapis.com" +``` + +Allow local images, embedded data images, and a dedicated image CDN: + +```bash +ALLOWED_IMAGE_SOURCES="data: https://images.example.com" +``` + +### Tightening Guidance + +When hardening a deployment: + +1. Start with defaults to avoid unexpected breakage. +2. Set explicit `ALLOWED_CSS_SOURCES` and `ALLOWED_IMAGE_SOURCES` values for the domains you actually use. +3. Test key workflows (editor, page display, theme assets, external embeds) and browser console CSP warnings. +4. Remove unnecessary protocols and hosts over time. + ## Code Standards We use tools to manage code standards and formatting within the project. If submitting a PR, formatting as per our project standards would help for clarity but don't worry too much about using/understanding these tools as we can always address issues at a later stage when they're picked up by our automated tools. diff --git a/readme.md b/readme.md index 00eb135046e..672cff55a41 100644 --- a/readme.md +++ b/readme.md @@ -102,6 +102,7 @@ Big thanks to these companies for supporting the project. ## 🛠️ Development & Testing Please see our [development docs](dev/docs/development.md) for full details regarding work on the BookStack source code. +For details on Content Security Policy controls (including image and CSS source options), see the **Content Security Policy Controls** section in the [development docs](dev/docs/development.md). If you're just looking to customize or extend your own BookStack instance, take a look at our [Hacking BookStack documentation page](https://www.bookstackapp.com/docs/admin/hacking-bookstack/) for details on various options to achieve this without altering the BookStack source code. diff --git a/tests/SecurityHeaderTest.php b/tests/SecurityHeaderTest.php index 3f4b7d193ce..126a85f238c 100644 --- a/tests/SecurityHeaderTest.php +++ b/tests/SecurityHeaderTest.php @@ -151,6 +151,42 @@ public function test_frame_src_csp_header_drawio_host_includes_port_if_existing( $this->assertEquals('frame-src \'self\' https://example.com https://diagrams.example.com:8080', $scriptHeader); } + public function test_style_src_csp_header_set_to_permissive_defaults_when_not_configured() + { + $resp = $this->get('/'); + $header = $this->getCspHeader($resp, 'style-src'); + + $this->assertEquals("style-src 'self' 'unsafe-inline' http: https:", $header); + } + + public function test_style_src_csp_header_can_be_overridden_by_config() + { + config()->set('app.css_sources', 'https://fonts.example.com'); + + $resp = $this->get('/'); + $header = $this->getCspHeader($resp, 'style-src'); + + $this->assertEquals("style-src 'self' https://fonts.example.com", $header); + } + + public function test_img_src_csp_header_set_to_permissive_defaults_when_not_configured() + { + $resp = $this->get('/'); + $header = $this->getCspHeader($resp, 'img-src'); + + $this->assertEquals("img-src 'self' data: blob: http: https:", $header); + } + + public function test_img_src_csp_header_can_be_overridden_by_config() + { + config()->set('app.image_sources', 'https://images.example.com data:'); + + $resp = $this->get('/'); + $header = $this->getCspHeader($resp, 'img-src'); + + $this->assertEquals("img-src 'self' https://images.example.com data:", $header); + } + public function test_cache_control_headers_are_set_on_responses() { // Public access From c7d3775bb999bda0d134de900b28f2ebeb4cec67 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 5 Apr 2026 00:05:10 +0100 Subject: [PATCH 095/204] Plain text: Created a new HTML to plain text converter To centralise logic to be more consistent, and to have smarter logic which avoids just following newline format from input, preventing smushing HTML elements (like list elements) next to eachother --- app/Activity/Models/Comment.php | 7 +++ .../Messages/CommentCreationNotification.php | 2 +- .../Messages/CommentMentionNotification.php | 2 +- app/Entities/Repos/BaseRepo.php | 4 +- app/Entities/Tools/PageContent.php | 5 +- app/Util/HtmlToPlainText.php | 47 ++++++++++++++ tests/Util/HtmlToPlainTextTest.php | 63 +++++++++++++++++++ 7 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 app/Util/HtmlToPlainText.php create mode 100644 tests/Util/HtmlToPlainTextTest.php diff --git a/app/Activity/Models/Comment.php b/app/Activity/Models/Comment.php index ab7d917729c..3faa76657b6 100644 --- a/app/Activity/Models/Comment.php +++ b/app/Activity/Models/Comment.php @@ -9,6 +9,7 @@ use BookStack\Users\Models\OwnableInterface; use BookStack\Util\HtmlContentFilter; use BookStack\Util\HtmlContentFilterConfig; +use BookStack\Util\HtmlToPlainText; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -87,6 +88,12 @@ public function safeHtml(): string return $filter->filterString($this->html ?? ''); } + public function getPlainText(): string + { + $converter = new HtmlToPlainText(); + return $converter->convert($this->html ?? ''); + } + public function jointPermissions(): HasMany { return $this->hasMany(JointPermission::class, 'entity_id', 'commentable_id') diff --git a/app/Activity/Notifications/Messages/CommentCreationNotification.php b/app/Activity/Notifications/Messages/CommentCreationNotification.php index 30d0ffa2be4..d739f4aabbf 100644 --- a/app/Activity/Notifications/Messages/CommentCreationNotification.php +++ b/app/Activity/Notifications/Messages/CommentCreationNotification.php @@ -24,7 +24,7 @@ public function toMail(User $notifiable): MailMessage $locale->trans('notifications.detail_page_name') => new EntityLinkMessageLine($page), $locale->trans('notifications.detail_page_path') => $this->buildPagePathLine($page, $notifiable), $locale->trans('notifications.detail_commenter') => $this->user->name, - $locale->trans('notifications.detail_comment') => strip_tags($comment->html), + $locale->trans('notifications.detail_comment') => $comment->getPlainText(), ]); return $this->newMailMessage($locale) diff --git a/app/Activity/Notifications/Messages/CommentMentionNotification.php b/app/Activity/Notifications/Messages/CommentMentionNotification.php index de9e719633d..4c8ee5bab8b 100644 --- a/app/Activity/Notifications/Messages/CommentMentionNotification.php +++ b/app/Activity/Notifications/Messages/CommentMentionNotification.php @@ -24,7 +24,7 @@ public function toMail(User $notifiable): MailMessage $locale->trans('notifications.detail_page_name') => new EntityLinkMessageLine($page), $locale->trans('notifications.detail_page_path') => $this->buildPagePathLine($page, $notifiable), $locale->trans('notifications.detail_commenter') => $this->user->name, - $locale->trans('notifications.detail_comment') => strip_tags($comment->html), + $locale->trans('notifications.detail_comment') => $comment->getPlainText(), ]); return $this->newMailMessage($locale) diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index 717e9c9f82a..44baeaccfdc 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -16,6 +16,7 @@ use BookStack\Sorting\BookSorter; use BookStack\Uploads\ImageRepo; use BookStack\Util\HtmlDescriptionFilter; +use BookStack\Util\HtmlToPlainText; use Illuminate\Http\UploadedFile; class BaseRepo @@ -151,9 +152,10 @@ protected function updateDescription(Entity $entity, array $input): void } if (isset($input['description_html'])) { + $plainTextConverter = new HtmlToPlainText(); $entity->descriptionInfo()->set( HtmlDescriptionFilter::filterFromString($input['description_html']), - html_entity_decode(strip_tags($input['description_html'])) + $plainTextConverter->convert($input['description_html']), ); } else if (isset($input['description'])) { $entity->descriptionInfo()->set('', $input['description']); diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index 8d89a86cff4..b86fbbe8bdd 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -16,6 +16,7 @@ use BookStack\Util\HtmlContentFilter; use BookStack\Util\HtmlContentFilterConfig; use BookStack\Util\HtmlDocument; +use BookStack\Util\HtmlToPlainText; use BookStack\Util\WebSafeMimeSniffer; use Closure; use DOMElement; @@ -303,8 +304,8 @@ protected function setUniqueId(DOMNode $element, array &$idMap): array public function toPlainText(): string { $html = $this->render(true); - - return html_entity_decode(strip_tags($html)); + $converter = new HtmlToPlainText(); + return $converter->convert($html); } /** diff --git a/app/Util/HtmlToPlainText.php b/app/Util/HtmlToPlainText.php new file mode 100644 index 00000000000..79da9e3d862 --- /dev/null +++ b/app/Util/HtmlToPlainText.php @@ -0,0 +1,47 @@ +nodeToText($doc->getBody()); + + // Remove repeated newlines + $text = preg_replace('/\n+/', "\n", $text); + // Remove leading/trailing whitespace + $text = trim($text); + + return $text; + } + + protected function nodeToText(\DOMNode $node): string + { + if ($node->nodeType === XML_TEXT_NODE) { + return $node->textContent; + } + + $text = ''; + if (!in_array($node->nodeName, $this->inlineTags)) { + $text .= "\n"; + } + + foreach ($node->childNodes as $childNode) { + $text .= $this->nodeToText($childNode); + } + + return $text; + } +} diff --git a/tests/Util/HtmlToPlainTextTest.php b/tests/Util/HtmlToPlainTextTest.php new file mode 100644 index 00000000000..e522e486360 --- /dev/null +++ b/tests/Util/HtmlToPlainTextTest.php @@ -0,0 +1,63 @@ +This is a test

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    +

    A Header

    +

    more <©> text with bold

    +HTML; + $expected = << text with bold +TEXT; + + $this->runTest($html, $expected); + } + + public function test_adjacent_list_items_are_separated_by_newline() + { + $html = <<
  • Item A
  • Item B
  • +HTML; + $expected = <<runTest($html, $expected); + } + + public function test_inline_formats_dont_cause_newlines() + { + $html = <<Hello

    +HTML; + $expected = <<runTest($html, $expected); + } + + protected function runTest(string $html, string $expected): void + { + $converter = new HtmlToPlainText(); + $result = $converter->convert(trim($html)); + $this->assertEquals(trim($expected), $result); + } +} From abed4eae0c541a44990d260796e21e420d8243ad Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 5 Apr 2026 17:51:19 +0100 Subject: [PATCH 096/204] Exports: Updated plaintext export to use new converter --- app/Exports/ExportFormatter.php | 20 +++++--------------- tests/Exports/TextExportTest.php | 4 ++-- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/app/Exports/ExportFormatter.php b/app/Exports/ExportFormatter.php index c5973eace29..dec8aa23d8f 100644 --- a/app/Exports/ExportFormatter.php +++ b/app/Exports/ExportFormatter.php @@ -11,6 +11,7 @@ use BookStack\Uploads\ImageService; use BookStack\Util\CspService; use BookStack\Util\HtmlDocument; +use BookStack\Util\HtmlToPlainText; use DOMElement; use Exception; use Throwable; @@ -242,24 +243,13 @@ protected function containHtml(string $htmlContent): string /** * Converts the page contents into simple plain text. - * This method filters any bad looking content to provide a nice final output. + * We re-generate the plain text from HTML at this point, post-page-content rendering. */ public function pageToPlainText(Page $page, bool $pageRendered = false, bool $fromParent = false): string { $html = $pageRendered ? $page->html : (new PageContent($page))->render(); - // Add proceeding spaces before tags so spaces remain between - // text within elements after stripping tags. - $html = str_replace('<', " <", $html); - $text = trim(strip_tags($html)); - // Replace multiple spaces with single spaces - $text = preg_replace('/ {2,}/', ' ', $text); - // Reduce multiple horrid whitespace characters. - $text = preg_replace('/(\x0A|\xA0|\x0A|\r|\n){2,}/su', "\n\n", $text); - $text = html_entity_decode($text); - // Add title - $text = $page->name . ($fromParent ? "\n" : "\n\n") . $text; - - return $text; + $contentText = (new HtmlToPlainText())->convert($html); + return $page->name . ($fromParent ? "\n" : "\n\n") . $contentText; } /** @@ -267,7 +257,7 @@ public function pageToPlainText(Page $page, bool $pageRendered = false, bool $fr */ public function chapterToPlainText(Chapter $chapter): string { - $text = $chapter->name . "\n" . $chapter->description; + $text = $chapter->name . "\n" . $chapter->descriptionInfo()->getPlain(); $text = trim($text) . "\n\n"; $parts = []; diff --git a/tests/Exports/TextExportTest.php b/tests/Exports/TextExportTest.php index 4b2d6288775..26298c185da 100644 --- a/tests/Exports/TextExportTest.php +++ b/tests/Exports/TextExportTest.php @@ -52,7 +52,7 @@ public function test_book_text_export_format() $resp = $this->asEditor()->get($entities['book']->getUrl('/export/plaintext')); $expected = "Export Book\nThis is a book with stuff to export\n\nExport chapter\nA test chapter to be exported\nIt has loads of info within\n\n"; - $expected .= "My wonderful page!\nMy great page Full of great stuff"; + $expected .= "My wonderful page!\nMy great page\nFull of great stuff"; $resp->assertSee($expected); } @@ -82,7 +82,7 @@ public function test_chapter_text_export_format() $resp = $this->asEditor()->get($entities['book']->getUrl('/export/plaintext')); $expected = "Export chapter\nA test chapter to be exported\nIt has loads of info within\n\n"; - $expected .= "My wonderful page!\nMy great page Full of great stuff"; + $expected .= "My wonderful page!\nMy great page\nFull of great stuff"; $resp->assertSee($expected); } } From b9d650785aee9be64a7e004c35bfa105de911a39 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 5 Apr 2026 22:28:27 +0100 Subject: [PATCH 097/204] Deps: Updated PHP package versions --- composer.lock | 341 +++++++++++++++++++++++++------------------------- 1 file changed, 171 insertions(+), 170 deletions(-) diff --git a/composer.lock b/composer.lock index d6069720d29..a70aa9a0ff9 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.373.7", + "version": "3.376.3", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "4402bd10f913e66b7271f44466be8d5ba6c9146e" + "reference": "2081f8db174df4bb8842aed3b7b513590ee9d219" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/4402bd10f913e66b7271f44466be8d5ba6c9146e", - "reference": "4402bd10f913e66b7271f44466be8d5ba6c9146e", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2081f8db174df4bb8842aed3b7b513590ee9d219", + "reference": "2081f8db174df4bb8842aed3b7b513590ee9d219", "shasum": "" }, "require": { @@ -153,22 +153,22 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.373.7" + "source": "https://github.com/aws/aws-sdk-php/tree/3.376.3" }, - "time": "2026-03-20T18:14:19+00:00" + "time": "2026-04-03T18:07:33+00:00" }, { "name": "bacon/bacon-qr-code", - "version": "v3.0.4", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/Bacon/BaconQrCode.git", - "reference": "3feed0e212b8412cc5d2612706744789b0615824" + "reference": "4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/3feed0e212b8412cc5d2612706744789b0615824", - "reference": "3feed0e212b8412cc5d2612706744789b0615824", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2", + "reference": "4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2", "shasum": "" }, "require": { @@ -208,9 +208,9 @@ "homepage": "https://github.com/Bacon/BaconQrCode", "support": { "issues": "https://github.com/Bacon/BaconQrCode/issues", - "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.4" + "source": "https://github.com/Bacon/BaconQrCode/tree/v3.1.1" }, - "time": "2026-03-16T01:01:30+00:00" + "time": "2026-04-05T21:06:35+00:00" }, { "name": "brick/math", @@ -982,16 +982,16 @@ }, { "name": "firebase/php-jwt", - "version": "v7.0.3", + "version": "v7.0.5", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e" + "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/28aa0694bcfdfa5e2959c394d5a1ee7a5083629e", - "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380", + "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380", "shasum": "" }, "require": { @@ -999,6 +999,7 @@ }, "require-dev": { "guzzlehttp/guzzle": "^7.4", + "phpfastcache/phpfastcache": "^9.2", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^9.5", "psr/cache": "^2.0||^3.0", @@ -1039,9 +1040,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v7.0.3" + "source": "https://github.com/firebase/php-jwt/tree/v7.0.5" }, - "time": "2026-02-25T22:16:40+00:00" + "time": "2026-04-01T20:38:03+00:00" }, { "name": "fruitcake/php-cors", @@ -1801,16 +1802,16 @@ }, { "name": "laravel/framework", - "version": "v12.55.1", + "version": "v12.56.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "6d9185a248d101b07eecaf8fd60b18129545fd33" + "reference": "dac16d424b59debb2273910dde88eb7050a2a709" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/6d9185a248d101b07eecaf8fd60b18129545fd33", - "reference": "6d9185a248d101b07eecaf8fd60b18129545fd33", + "url": "https://api.github.com/repos/laravel/framework/zipball/dac16d424b59debb2273910dde88eb7050a2a709", + "reference": "dac16d424b59debb2273910dde88eb7050a2a709", "shasum": "" }, "require": { @@ -2019,20 +2020,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-03-18T14:28:59+00:00" + "time": "2026-03-26T14:51:54+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.15", + "version": "v0.3.16", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "4bb8107ec97651fd3f17f897d6489dbc4d8fb999" + "reference": "11e7d5f93803a2190b00e145142cb00a33d17ad2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/4bb8107ec97651fd3f17f897d6489dbc4d8fb999", - "reference": "4bb8107ec97651fd3f17f897d6489dbc4d8fb999", + "url": "https://api.github.com/repos/laravel/prompts/zipball/11e7d5f93803a2190b00e145142cb00a33d17ad2", + "reference": "11e7d5f93803a2190b00e145142cb00a33d17ad2", "shasum": "" }, "require": { @@ -2076,9 +2077,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.15" + "source": "https://github.com/laravel/prompts/tree/v0.3.16" }, - "time": "2026-03-17T13:45:17+00:00" + "time": "2026-03-23T14:35:33+00:00" }, { "name": "laravel/serializable-closure", @@ -2143,16 +2144,16 @@ }, { "name": "laravel/socialite", - "version": "v5.25.0", + "version": "v5.26.1", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "231f572e1a37c9ca1fb8085e9fb8608285beafb3" + "reference": "db6ec2ee967b7f06412c3a0cf1daaf072f4752a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/231f572e1a37c9ca1fb8085e9fb8608285beafb3", - "reference": "231f572e1a37c9ca1fb8085e9fb8608285beafb3", + "url": "https://api.github.com/repos/laravel/socialite/zipball/db6ec2ee967b7f06412c3a0cf1daaf072f4752a4", + "reference": "db6ec2ee967b7f06412c3a0cf1daaf072f4752a4", "shasum": "" }, "require": { @@ -2211,7 +2212,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2026-02-27T13:56:35+00:00" + "time": "2026-03-29T14:50:53+00:00" }, { "name": "laravel/tinker", @@ -2470,16 +2471,16 @@ }, { "name": "league/flysystem", - "version": "3.32.0", + "version": "3.33.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725" + "reference": "570b8871e0ce693764434b29154c54b434905350" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/254b1595b16b22dbddaaef9ed6ca9fdac4956725", - "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/570b8871e0ce693764434b29154c54b434905350", + "reference": "570b8871e0ce693764434b29154c54b434905350", "shasum": "" }, "require": { @@ -2547,9 +2548,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.32.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.33.0" }, - "time": "2026-02-25T17:01:41+00:00" + "time": "2026-03-25T07:59:30+00:00" }, { "name": "league/flysystem-aws-s3-v3", @@ -4664,16 +4665,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.21", + "version": "v0.12.22", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97" + "reference": "3be75d5b9244936dd4ac62ade2bfb004d13acf0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", - "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/3be75d5b9244936dd4ac62ade2bfb004d13acf0f", + "reference": "3be75d5b9244936dd4ac62ade2bfb004d13acf0f", "shasum": "" }, "require": { @@ -4737,9 +4738,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.21" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.22" }, - "time": "2026-03-06T21:21:28+00:00" + "time": "2026-03-22T23:03:24+00:00" }, { "name": "ralouphie/getallheaders", @@ -5431,16 +5432,16 @@ }, { "name": "symfony/clock", - "version": "v7.4.0", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110" + "reference": "674fa3b98e21531dd040e613479f5f6fa8f32111" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110", + "url": "https://api.github.com/repos/symfony/clock/zipball/674fa3b98e21531dd040e613479f5f6fa8f32111", + "reference": "674fa3b98e21531dd040e613479f5f6fa8f32111", "shasum": "" }, "require": { @@ -5485,7 +5486,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.4.0" + "source": "https://github.com/symfony/clock/tree/v7.4.8" }, "funding": [ { @@ -5505,20 +5506,20 @@ "type": "tidelift" } ], - "time": "2025-11-12T15:39:26+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/console", - "version": "v7.4.7", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" + "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", - "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "url": "https://api.github.com/repos/symfony/console/zipball/1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707", + "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707", "shasum": "" }, "require": { @@ -5583,7 +5584,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.7" + "source": "https://github.com/symfony/console/tree/v7.4.8" }, "funding": [ { @@ -5603,20 +5604,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T14:06:20+00:00" + "time": "2026-03-30T13:54:39+00:00" }, { "name": "symfony/css-selector", - "version": "v7.4.6", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "2e7c52c647b406e2107dd867db424a4dbac91864" + "reference": "b055f228a4178a1d6774909903905e3475f3eac8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/2e7c52c647b406e2107dd867db424a4dbac91864", - "reference": "2e7c52c647b406e2107dd867db424a4dbac91864", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/b055f228a4178a1d6774909903905e3475f3eac8", + "reference": "b055f228a4178a1d6774909903905e3475f3eac8", "shasum": "" }, "require": { @@ -5652,7 +5653,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.6" + "source": "https://github.com/symfony/css-selector/tree/v7.4.8" }, "funding": [ { @@ -5672,7 +5673,7 @@ "type": "tidelift" } ], - "time": "2026-02-17T07:53:42+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5743,16 +5744,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.4.4", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8" + "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8", - "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa", + "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa", "shasum": "" }, "require": { @@ -5801,7 +5802,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.4.4" + "source": "https://github.com/symfony/error-handler/tree/v7.4.8" }, "funding": [ { @@ -5821,20 +5822,20 @@ "type": "tidelift" } ], - "time": "2026-01-20T16:42:42+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.4", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "dc2c0eba1af673e736bb851d747d266108aea746" + "reference": "f57b899fa736fd71121168ef268f23c206083f0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746", - "reference": "dc2c0eba1af673e736bb851d747d266108aea746", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f57b899fa736fd71121168ef268f23c206083f0a", + "reference": "f57b899fa736fd71121168ef268f23c206083f0a", "shasum": "" }, "require": { @@ -5886,7 +5887,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.4" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.8" }, "funding": [ { @@ -5906,7 +5907,7 @@ "type": "tidelift" } ], - "time": "2026-01-05T11:45:34+00:00" + "time": "2026-03-30T13:54:39+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -5986,16 +5987,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.4.6", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e" + "reference": "58b9790d12f9670b7f53a1c1738febd3108970a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/3ebc794fa5315e59fd122561623c2e2e4280538e", - "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/58b9790d12f9670b7f53a1c1738febd3108970a5", + "reference": "58b9790d12f9670b7f53a1c1738febd3108970a5", "shasum": "" }, "require": { @@ -6032,7 +6033,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.6" + "source": "https://github.com/symfony/filesystem/tree/v7.4.8" }, "funding": [ { @@ -6052,20 +6053,20 @@ "type": "tidelift" } ], - "time": "2026-02-25T16:50:00+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/finder", - "version": "v7.4.6", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" + "reference": "e0be088d22278583a82da281886e8c3592fbf149" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", - "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "url": "https://api.github.com/repos/symfony/finder/zipball/e0be088d22278583a82da281886e8c3592fbf149", + "reference": "e0be088d22278583a82da281886e8c3592fbf149", "shasum": "" }, "require": { @@ -6100,7 +6101,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.6" + "source": "https://github.com/symfony/finder/tree/v7.4.8" }, "funding": [ { @@ -6120,20 +6121,20 @@ "type": "tidelift" } ], - "time": "2026-01-29T09:40:50+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.7", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81" + "reference": "9381209597ec66c25be154cbf2289076e64d1eab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f94b3e7b7dafd40e666f0c9ff2084133bae41e81", - "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9381209597ec66c25be154cbf2289076e64d1eab", + "reference": "9381209597ec66c25be154cbf2289076e64d1eab", "shasum": "" }, "require": { @@ -6182,7 +6183,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.7" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.8" }, "funding": [ { @@ -6202,20 +6203,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T13:15:18+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.7", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1" + "reference": "017e76ad089bac281553389269e259e155935e1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3b3fcf386c809be990c922e10e4c620d6367cab1", - "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/017e76ad089bac281553389269e259e155935e1a", + "reference": "017e76ad089bac281553389269e259e155935e1a", "shasum": "" }, "require": { @@ -6301,7 +6302,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.7" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.8" }, "funding": [ { @@ -6321,20 +6322,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T16:33:18+00:00" + "time": "2026-03-31T20:57:01+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.6", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9" + "reference": "f6ea532250b476bfc1b56699b388a1bdbf168f62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9", - "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "url": "https://api.github.com/repos/symfony/mailer/zipball/f6ea532250b476bfc1b56699b388a1bdbf168f62", + "reference": "f6ea532250b476bfc1b56699b388a1bdbf168f62", "shasum": "" }, "require": { @@ -6385,7 +6386,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.6" + "source": "https://github.com/symfony/mailer/tree/v7.4.8" }, "funding": [ { @@ -6405,20 +6406,20 @@ "type": "tidelift" } ], - "time": "2026-02-25T16:50:00+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/mime", - "version": "v7.4.7", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1" + "reference": "6df02f99998081032da3407a8d6c4e1dcb5d4379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/da5ab4fde3f6c88ab06e96185b9922f48b677cd1", - "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "url": "https://api.github.com/repos/symfony/mime/zipball/6df02f99998081032da3407a8d6c4e1dcb5d4379", + "reference": "6df02f99998081032da3407a8d6c4e1dcb5d4379", "shasum": "" }, "require": { @@ -6474,7 +6475,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.7" + "source": "https://github.com/symfony/mime/tree/v7.4.8" }, "funding": [ { @@ -6494,7 +6495,7 @@ "type": "tidelift" } ], - "time": "2026-03-05T15:24:09+00:00" + "time": "2026-03-30T14:11:46+00:00" }, { "name": "symfony/polyfill-ctype", @@ -7327,16 +7328,16 @@ }, { "name": "symfony/process", - "version": "v7.4.5", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "608476f4604102976d687c483ac63a79ba18cc97" + "reference": "60f19cd3badc8de688421e21e4305eba50f8089a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", - "reference": "608476f4604102976d687c483ac63a79ba18cc97", + "url": "https://api.github.com/repos/symfony/process/zipball/60f19cd3badc8de688421e21e4305eba50f8089a", + "reference": "60f19cd3badc8de688421e21e4305eba50f8089a", "shasum": "" }, "require": { @@ -7368,7 +7369,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.5" + "source": "https://github.com/symfony/process/tree/v7.4.8" }, "funding": [ { @@ -7388,20 +7389,20 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:07:59+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/routing", - "version": "v7.4.6", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "238d749c56b804b31a9bf3e26519d93b65a60938" + "reference": "9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/238d749c56b804b31a9bf3e26519d93b65a60938", - "reference": "238d749c56b804b31a9bf3e26519d93b65a60938", + "url": "https://api.github.com/repos/symfony/routing/zipball/9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b", + "reference": "9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b", "shasum": "" }, "require": { @@ -7453,7 +7454,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.6" + "source": "https://github.com/symfony/routing/tree/v7.4.8" }, "funding": [ { @@ -7473,7 +7474,7 @@ "type": "tidelift" } ], - "time": "2026-02-25T16:50:00+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/service-contracts", @@ -7564,16 +7565,16 @@ }, { "name": "symfony/string", - "version": "v7.4.6", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" + "reference": "114ac57257d75df748eda23dd003878080b8e688" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", + "url": "https://api.github.com/repos/symfony/string/zipball/114ac57257d75df748eda23dd003878080b8e688", + "reference": "114ac57257d75df748eda23dd003878080b8e688", "shasum": "" }, "require": { @@ -7631,7 +7632,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.6" + "source": "https://github.com/symfony/string/tree/v7.4.8" }, "funding": [ { @@ -7651,20 +7652,20 @@ "type": "tidelift" } ], - "time": "2026-02-09T09:33:46+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/translation", - "version": "v7.4.6", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "1888cf064399868af3784b9e043240f1d89d25ce" + "reference": "33600f8489485425bfcddd0d983391038d3422e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/1888cf064399868af3784b9e043240f1d89d25ce", - "reference": "1888cf064399868af3784b9e043240f1d89d25ce", + "url": "https://api.github.com/repos/symfony/translation/zipball/33600f8489485425bfcddd0d983391038d3422e7", + "reference": "33600f8489485425bfcddd0d983391038d3422e7", "shasum": "" }, "require": { @@ -7731,7 +7732,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.6" + "source": "https://github.com/symfony/translation/tree/v7.4.8" }, "funding": [ { @@ -7751,7 +7752,7 @@ "type": "tidelift" } ], - "time": "2026-02-17T07:53:42+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/translation-contracts", @@ -7837,16 +7838,16 @@ }, { "name": "symfony/uid", - "version": "v7.4.4", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36" + "reference": "6883ebdf7bf6a12b37519dbc0df62b0222401b56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/7719ce8aba76be93dfe249192f1fbfa52c588e36", - "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36", + "url": "https://api.github.com/repos/symfony/uid/zipball/6883ebdf7bf6a12b37519dbc0df62b0222401b56", + "reference": "6883ebdf7bf6a12b37519dbc0df62b0222401b56", "shasum": "" }, "require": { @@ -7891,7 +7892,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.4.4" + "source": "https://github.com/symfony/uid/tree/v7.4.8" }, "funding": [ { @@ -7911,20 +7912,20 @@ "type": "tidelift" } ], - "time": "2026-01-03T23:30:35+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.4.6", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291" + "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291", - "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9510c3966f749a1d1ff0059e1eabef6cc621e7fd", + "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd", "shasum": "" }, "require": { @@ -7978,7 +7979,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.6" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.8" }, "funding": [ { @@ -7998,7 +7999,7 @@ "type": "tidelift" } ], - "time": "2026-02-15T10:53:20+00:00" + "time": "2026-03-30T13:44:50+00:00" }, { "name": "thecodingmachine/safe", @@ -8955,23 +8956,23 @@ }, { "name": "nunomaduro/collision", - "version": "v8.9.1", + "version": "v8.9.2", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935" + "reference": "6eb16883e74fd725ac64dbe81544c961ab448ba5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", - "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/6eb16883e74fd725ac64dbe81544c961ab448ba5", + "reference": "6eb16883e74fd725ac64dbe81544c961ab448ba5", "shasum": "" }, "require": { "filp/whoops": "^2.18.4", "nunomaduro/termwind": "^2.4.0", "php": "^8.2.0", - "symfony/console": "^7.4.4 || ^8.0.4" + "symfony/console": "^7.4.8 || ^8.0.4" }, "conflict": { "laravel/framework": "<11.48.0 || >=14.0.0", @@ -8979,12 +8980,12 @@ }, "require-dev": { "brianium/paratest": "^7.8.5", - "larastan/larastan": "^3.9.2", - "laravel/framework": "^11.48.0 || ^12.52.0", - "laravel/pint": "^1.27.1", - "orchestra/testbench-core": "^9.12.0 || ^10.9.0", - "pestphp/pest": "^3.8.5 || ^4.4.1 || ^5.0.0", - "sebastian/environment": "^7.2.1 || ^8.0.3 || ^9.0.0" + "larastan/larastan": "^3.9.3", + "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.2.0", + "laravel/pint": "^1.29.0", + "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.0.0", + "pestphp/pest": "^3.8.5 || ^4.4.3 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.0.4 || ^9.0.0" }, "type": "library", "extra": { @@ -9047,7 +9048,7 @@ "type": "patreon" } ], - "time": "2026-02-17T17:33:08+00:00" + "time": "2026-03-31T21:51:27+00:00" }, { "name": "phar-io/manifest", @@ -9169,11 +9170,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.42", + "version": "2.1.46", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1279e1ce86ba768f0780c9d889852b4e02ff40d0", - "reference": "1279e1ce86ba768f0780c9d889852b4e02ff40d0", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a193923fc2d6325ef4e741cf3af8c3e8f54dbf25", + "reference": "a193923fc2d6325ef4e741cf3af8c3e8f54dbf25", "shasum": "" }, "require": { @@ -9218,7 +9219,7 @@ "type": "github" } ], - "time": "2026-03-17T14:58:32+00:00" + "time": "2026-04-01T09:25:14+00:00" }, { "name": "phpunit/php-code-coverage", @@ -10841,16 +10842,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v7.4.6", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e" + "reference": "2918e7c2ba964defca1f5b69c6f74886529e2dc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/487ba8fa43da9a8e6503fe939b45ecd96875410e", - "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2918e7c2ba964defca1f5b69c6f74886529e2dc8", + "reference": "2918e7c2ba964defca1f5b69c6f74886529e2dc8", "shasum": "" }, "require": { @@ -10889,7 +10890,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.4.6" + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.8" }, "funding": [ { @@ -10909,7 +10910,7 @@ "type": "tidelift" } ], - "time": "2026-02-17T07:53:42+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "theseer/tokenizer", From a7dd998ac9ef4614966d690813472ab32b7c8e58 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 5 Apr 2026 22:29:00 +0100 Subject: [PATCH 098/204] Updated translations with latest Crowdin changes (#6067) --- lang/da/editor.php | 2 +- lang/da/entities.php | 8 +- lang/da/errors.php | 2 +- lang/da/settings.php | 2 +- lang/de/entities.php | 228 ++++++++++++++-------------- lang/de/settings.php | 275 +++++++++++++++++----------------- lang/de_informal/entities.php | 8 +- lang/de_informal/settings.php | 12 +- lang/ko/editor.php | 2 +- lang/ko/errors.php | 4 +- lang/pt/common.php | 4 +- lang/pt/editor.php | 4 +- lang/pt/entities.php | 22 +-- 13 files changed, 286 insertions(+), 287 deletions(-) diff --git a/lang/da/editor.php b/lang/da/editor.php index f135f06a90b..628e1319e21 100644 --- a/lang/da/editor.php +++ b/lang/da/editor.php @@ -36,7 +36,7 @@ 'paragraph' => 'Paragraf', 'blockquote' => 'Citat', 'inline_code' => 'Inline kode', - 'callouts' => 'Callouts', + 'callouts' => 'Tekstfelter', 'callout_information' => 'Information', 'callout_success' => 'Succes', 'callout_warning' => 'Advarsel', diff --git a/lang/da/entities.php b/lang/da/entities.php index 23cc94626c5..ecda8a8cf8a 100644 --- a/lang/da/entities.php +++ b/lang/da/entities.php @@ -63,10 +63,10 @@ 'import_delete_desc' => 'Dette vil slette den uploadede ZIP-fil og kan ikke fortrydes.', 'import_errors' => 'Importfejl', 'import_errors_desc' => 'Følgende fejl opstod under importforsøget:', - 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', - 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter', - 'breadcrumb_siblings_for_book' => 'Navigate siblings for book', - 'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf', + 'breadcrumb_siblings_for_page' => 'Naviger blandt siderne', + 'breadcrumb_siblings_for_chapter' => 'Gå til næste eller forrige kapitel', + 'breadcrumb_siblings_for_book' => 'Gennemse søskende til bogen', + 'breadcrumb_siblings_for_bookshelf' => 'Gennemse undermapper til hylden', // Permissions and restrictions 'permissions' => 'Rettigheder', diff --git a/lang/da/errors.php b/lang/da/errors.php index eeb96f59b8d..62a275547ff 100644 --- a/lang/da/errors.php +++ b/lang/da/errors.php @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => 'Hemmeligheden leveret til det givne anvendte API-token er forkert', 'api_user_no_api_permission' => 'Ejeren af den brugte API token har ikke adgang til at foretage API-kald', 'api_user_token_expired' => 'Den brugte godkendelsestoken er udløbet', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Der tillades kun GET-anmodninger, når API\'et bruges med cookie-baseret godkendelse', // Settings & Maintenance 'maintenance_test_email_failure' => 'Følgende fejl opstod under afsendelse af testemail:', diff --git a/lang/da/settings.php b/lang/da/settings.php index cd869c62fe0..1edf10d0ec9 100644 --- a/lang/da/settings.php +++ b/lang/da/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Kapitler først', 'sort_rule_op_chapters_last' => 'De sidste kapitler', 'sorting_page_limits' => 'Visningsgrænser pr. side', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sorting_page_limits_desc' => 'Indstil, hvor mange poster der skal vises pr. side i de forskellige lister i systemet. Normalt giver et lavere antal bedre ydeevne, mens et højere antal undgår, at man skal klikke sig igennem flere sider. Det anbefales at vælge et tal, der er et multiplum af 6.', // Maintenance settings 'maint' => 'Vedligeholdelse', diff --git a/lang/de/entities.php b/lang/de/entities.php index 94c327b7e06..52f9b7acad1 100644 --- a/lang/de/entities.php +++ b/lang/de/entities.php @@ -23,175 +23,175 @@ 'meta_updated' => 'Zuletzt aktualisiert: :timeLength', 'meta_updated_name' => 'Zuletzt aktualisiert: :timeLength von :user', 'meta_owned_name' => 'Im Besitz von :user', - 'meta_reference_count' => 'Referenziert von :count Element|Referenziert von :count Elementen', + 'meta_reference_count' => 'Verwiesen von :count Element|verwiesen von :count Elementen', 'entity_select' => 'Eintrag auswählen', - 'entity_select_lack_permission' => 'Sie haben nicht die benötigte Berechtigung, um dieses Element auszuwählen', + 'entity_select_lack_permission' => 'Sie verfügen nicht über die erforderlichen Berechtigungen, um dieses Element auszuwählen', 'images' => 'Bilder', - 'my_recent_drafts' => 'Meine kürzlichen Entwürfe', - 'my_recently_viewed' => 'Kürzlich von mir angesehen', + 'my_recent_drafts' => 'Meine letzten Entwürfe', + 'my_recently_viewed' => 'Meine zuletzt angesehenen', 'my_most_viewed_favourites' => 'Meine meistgesehenen Favoriten', 'my_favourites' => 'Meine Favoriten', - 'no_pages_viewed' => 'Sie haben bisher keine Seiten angesehen', - 'no_pages_recently_created' => 'Sie haben bisher keine Seiten angelegt', - 'no_pages_recently_updated' => 'Sie haben bisher keine Seiten aktualisiert', + 'no_pages_viewed' => 'Sie haben noch keine Seiten aufgerufen', + 'no_pages_recently_created' => 'Es wurden in letzter Zeit keine Seiten erstellt', + 'no_pages_recently_updated' => 'Es wurden in letzter Zeit keine Seiten aktualisiert', 'export' => 'Exportieren', - 'export_html' => 'HTML-Datei', + 'export_html' => 'Eingebettete Webdatei', 'export_pdf' => 'PDF-Datei', - 'export_text' => 'Textdatei', + 'export_text' => 'Klartextdatei', 'export_md' => 'Markdown-Datei', - 'export_zip' => 'Portable ZIP', + 'export_zip' => 'Portables ZIP', 'default_template' => 'Standard-Seitenvorlage', - 'default_template_explain' => 'Bestimmen Sie eine Seitenvorlage, die als Standardinhalt für alle Seiten verwendet wird, die innerhalb dieses Elements erstellt werden. Beachten Sie, dass dies nur dann verwendet wird, wenn der Ersteller der Seite Lesezugriff auf die ausgewählte Vorlagen-Seite hat.', + 'default_template_explain' => 'Weisen Sie eine Seitenvorlage zu, die als Standardinhalt für alle innerhalb dieses Elements erstellten Seiten verwendet wird. Beachten Sie, dass diese nur verwendet wird, wenn der Ersteller der Seite über Anzeigerechte für die ausgewählte Vorlagenseite verfügt.', 'default_template_select' => 'Wählen Sie eine Seitenvorlage', - 'import' => 'Import', - 'import_validate' => 'Import validieren', - 'import_desc' => 'Importieren Sie Bücher, Kapitel & Seiten mit einem "Portable Zip-Export" von der gleichen oder einer anderen Instanz. Wählen Sie eine ZIP-Datei, um fortzufahren. Nachdem die Datei hochgeladen und bestätigt wurde, können Sie den Import in der nächsten Ansicht konfigurieren und bestätigen.', - 'import_zip_select' => 'ZIP-Datei zum Hochladen auswählen', - 'import_zip_validation_errors' => 'Fehler bei der Validierung der angegebenen ZIP-Datei:', + 'import' => 'Importieren', + 'import_validate' => 'Import bestätigen', + 'import_desc' => 'Importieren Sie Bücher, Kapitel und Seiten mithilfe eines portablen ZIP-Exports aus derselben oder einer anderen Instanz. Wählen Sie eine ZIP-Datei aus, um fortzufahren. Nachdem die Datei hochgeladen und überprüft wurde, können Sie den Import in der nächsten Ansicht konfigurieren und bestätigen.', + 'import_zip_select' => 'Wähle eine ZIP-Datei zum Hochladen aus', + 'import_zip_validation_errors' => 'Bei der Überprüfung der bereitgestellten ZIP-Datei wurden Fehler festgestellt:', 'import_pending' => 'Ausstehende Importe', 'import_pending_none' => 'Es wurden keine Importe gestartet.', 'import_continue' => 'Import fortsetzen', - 'import_continue_desc' => 'Überprüfen Sie den Inhalt, der aus der hochgeladenen ZIP-Datei importiert werden soll. Führen Sie den Import aus, um dessen Inhalt zu diesem System hinzuzufügen. Die hochgeladene ZIP-Importdatei wird bei erfolgreichem Import automatisch entfernt.', - 'import_details' => 'Einzelheiten zum Import', - 'import_run' => 'Import starten', + 'import_continue_desc' => 'Überprüfen Sie den Inhalt, der aus der hochgeladenen ZIP-Datei importiert werden soll. Wenn Sie bereit sind, starten Sie den Import, um den Inhalt in dieses System zu übernehmen. Die hochgeladene ZIP-Importdatei wird nach erfolgreichem Import automatisch gelöscht.', + 'import_details' => 'Importdetails', + 'import_run' => 'Import ausführen', 'import_size' => ':size Import ZIP Größe', 'import_uploaded_at' => 'Hochgeladen :relativeTime', 'import_uploaded_by' => 'Hochgeladen von', - 'import_location' => 'Import Ort', - 'import_location_desc' => 'Wählen Sie einen Zielort für Ihren importierten Inhalt. Sie benötigen die entsprechenden Berechtigungen, um innerhalb des gewünschten Standortes zu erstellen.', - 'import_delete_confirm' => 'Sind Sie sicher, dass Sie diesen Import löschen möchten?', - 'import_delete_desc' => 'Dies löscht die hochgeladene ZIP-Datei und kann nicht rückgängig gemacht werden.', + 'import_location' => 'Importort', + 'import_location_desc' => 'Wählen Sie einen Zielspeicherort für Ihre importierten Inhalte aus. Sie benötigen die entsprechenden Berechtigungen, um an dem von Ihnen gewählten Speicherort Inhalte zu erstellen.', + 'import_delete_confirm' => 'Möchten Sie diesen Import wirklich löschen?', + 'import_delete_desc' => 'Dadurch wird die hochgeladene ZIP-Importdatei gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden.', 'import_errors' => 'Importfehler', - 'import_errors_desc' => 'Die folgenden Fehler sind während des Importversuchs aufgetreten:', - 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', - 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter', - 'breadcrumb_siblings_for_book' => 'Navigiere in Büchern', - 'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf', + 'import_errors_desc' => 'Beim Importversuch sind folgende Fehler aufgetreten:', + 'breadcrumb_siblings_for_page' => 'Durch die untergeordneten Elemente der Seite navigieren', + 'breadcrumb_siblings_for_chapter' => 'Durch die Unterelemente des Kapitels navigieren', + 'breadcrumb_siblings_for_book' => 'Durch die Unterordner des Buches navigieren', + 'breadcrumb_siblings_for_bookshelf' => 'Durch die untergeordneten Elemente des Regals navigieren', // Permissions and restrictions 'permissions' => 'Berechtigungen', - 'permissions_desc' => 'Legen Sie hier Berechtigungen fest, um die Standardberechtigungen von Benutzerrollen zu überschreiben.', + 'permissions_desc' => 'Legen Sie hier Berechtigungen fest, um die durch Benutzerrollen vorgegebenen Standardberechtigungen zu überschreiben.', 'permissions_book_cascade' => 'In Büchern festgelegte Berechtigungen werden automatisch in untergeordnete Kapitel und Seiten kaskadiert, es sei denn, sie haben eigene Berechtigungen definiert.', - 'permissions_chapter_cascade' => 'In Kapiteln festgelegte Berechtigungen werden automatisch in untergeordnete Seiten kaskadiert, es sei denn, sie haben eigene Berechtigungen definiert.', + 'permissions_chapter_cascade' => 'Die für Kapitel festgelegten Berechtigungen werden automatisch auf untergeordnete Seiten übertragen, sofern für diese keine eigenen Berechtigungen definiert sind.', 'permissions_save' => 'Berechtigungen speichern', 'permissions_owner' => 'Besitzer', 'permissions_role_everyone_else' => 'Alle anderen', - 'permissions_role_everyone_else_desc' => 'Berechtigungen für alle Rollen setzen, die nicht explizit überschrieben wurden.', - 'permissions_role_override' => 'Berechtigungen für Rolle überschreiben', - 'permissions_inherit_defaults' => 'Standardeinstellungen vererben', + 'permissions_role_everyone_else_desc' => 'Berechtigungen für alle Rollen festlegen, die nicht ausdrücklich überschrieben wurden.', + 'permissions_role_override' => 'Berechtigungen für eine Rolle überschreiben', + 'permissions_inherit_defaults' => 'Standardwerte übernehmen', // Search 'search_results' => 'Suchergebnisse', - 'search_total_results_found' => ':count Ergebnis gefunden|:count Ergebnisse gesamt', - 'search_clear' => 'Filter löschen', - 'search_no_pages' => 'Keine Seiten gefunden', + 'search_total_results_found' => ':count Ergebnisse gefunden|:count insgesamt gefundene Ergebnisse', + 'search_clear' => 'Suche löschen', + 'search_no_pages' => 'Es wurden keine Seiten gefunden, die dieser Suche entsprechen', 'search_for_term' => 'Nach :term suchen', 'search_more' => 'Mehr Ergebnisse', 'search_advanced' => 'Erweiterte Suche', 'search_terms' => 'Suchbegriffe', 'search_content_type' => 'Inhaltstyp', - 'search_exact_matches' => 'Exakte Treffer', + 'search_exact_matches' => 'Genau übereinstimmende Treffer', 'search_tags' => 'Schlagwort-Suchen', 'search_options' => 'Optionen', - 'search_viewed_by_me' => 'Schon von mir angesehen', - 'search_not_viewed_by_me' => 'Noch nicht von mir angesehen', + 'search_viewed_by_me' => 'Von mir angesehen', + 'search_not_viewed_by_me' => 'Von mir nicht angesehen', 'search_permissions_set' => 'Berechtigungen gesetzt', 'search_created_by_me' => 'Von mir erstellt', 'search_updated_by_me' => 'Von mir aktualisiert', 'search_owned_by_me' => 'In meinem Besitz', 'search_date_options' => 'Datums Optionen', - 'search_updated_before' => 'Aktualisiert vor', + 'search_updated_before' => 'Zuletzt aktualisiert am', 'search_updated_after' => 'Aktualisiert nach', 'search_created_before' => 'Erstellt vor', 'search_created_after' => 'Erstellt nach', - 'search_set_date' => 'Datum auswählen', + 'search_set_date' => 'Datum festlegen', 'search_update' => 'Suche aktualisieren', // Shelves 'shelf' => 'Regal', 'shelves' => 'Regale', 'x_shelves' => ':count Regal|:count Regale', - 'shelves_empty' => 'Es wurden noch keine Regale angelegt', - 'shelves_create' => 'Erzeuge ein Regal', + 'shelves_empty' => 'Es wurden keine Regale angelegt', + 'shelves_create' => 'Neues Regal erstellen', 'shelves_popular' => 'Beliebte Regale', 'shelves_new' => 'Kürzlich erstellte Regale', 'shelves_new_action' => 'Neues Regal', 'shelves_popular_empty' => 'Die beliebtesten Regale werden hier angezeigt.', - 'shelves_new_empty' => 'Die neusten Regale werden hier angezeigt.', + 'shelves_new_empty' => 'Hier werden die zuletzt erstellten Regale angezeigt.', 'shelves_save' => 'Regal speichern', 'shelves_books' => 'Bücher in diesem Regal', 'shelves_add_books' => 'Buch zu diesem Regal hinzufügen', - 'shelves_drag_books' => 'Ziehen Sie Bücher nach unten, um sie diesem Regal hinzuzufügen', + 'shelves_drag_books' => 'Ziehe die unten stehenden Bücher per Drag-and-drop auf dieses Regal, um sie hinzuzufügen', 'shelves_empty_contents' => 'Diesem Regal sind keine Bücher zugewiesen', - 'shelves_edit_and_assign' => 'Regal bearbeiten um Bücher hinzuzufügen', + 'shelves_edit_and_assign' => 'Regal bearbeiten, um Bücher zuzuordnen', 'shelves_edit_named' => 'Regal :name bearbeiten', 'shelves_edit' => 'Regal bearbeiten', 'shelves_delete' => 'Regal löschen', 'shelves_delete_named' => 'Regal :name löschen', - 'shelves_delete_explain' => "Dadurch wird das Regal mit dem Namen ':name' gelöscht. Die darin enthaltenen Bücher werden nicht gelöscht.", - 'shelves_delete_confirmation' => 'Sind Sie sicher, dass Sie dieses Regal löschen möchten?', - 'shelves_permissions' => 'Regalberechtigungen', - 'shelves_permissions_updated' => 'Regalberechtigungen aktualisiert', - 'shelves_permissions_active' => 'Regalberechtigungen aktiv', - 'shelves_permissions_cascade_warning' => 'Berechtigungen für Regale werden nicht automatisch auf die enthaltenen Bücher übertragen. Das liegt daran, dass ein Buch in mehreren Regalen vorhanden sein kann. Berechtigungen können jedoch auf untergeordnete Bücher kopiert werden, indem Sie die unten stehende Option verwenden.', - 'shelves_permissions_create' => 'Regalerstellungsberechtigungen werden nur zum Kopieren von Berechtigungen für untergeordnete Bücher mit der folgenden Aktion verwendet. Sie kontrollieren nicht die Fähigkeit, Bücher zu erstellen.', - 'shelves_copy_permissions_to_books' => 'Kopiere die Berechtigungen zum Buch', - 'shelves_copy_permissions' => 'Berechtigungen kopieren', - 'shelves_copy_permissions_explain' => 'Dadurch werden die aktuellen Berechtigungseinstellungen dieses Regals auf alle darin enthaltenen Bücher angewendet. Vergewissern Sie sich vor der Aktivierung, dass alle Änderungen an den Berechtigungen für dieses Regal gespeichert wurden.', - 'shelves_copy_permission_success' => 'Regalberechtigungen auf :count Bücher kopiert', + 'shelves_delete_explain' => "Dadurch wird das Regal mit dem Namen „:name“ gelöscht. Die darin enthaltenen Bücher werden nicht gelöscht.", + 'shelves_delete_confirmation' => 'Möchtest du dieses Regal wirklich löschen?', + 'shelves_permissions' => 'Berechtigungen für Regale', + 'shelves_permissions_updated' => 'Berechtigungen für Regale aktualisiert', + 'shelves_permissions_active' => 'Regal Berechtigungen aktiv', + 'shelves_permissions_cascade_warning' => 'Berechtigungen für Regale werden nicht automatisch auf die darin enthaltenen Bücher übertragen. Das liegt daran, dass ein Buch in mehreren Regalen stehen kann. Berechtigungen können jedoch mithilfe der unten aufgeführten Option auf untergeordnete Bücher übertragen werden.', + 'shelves_permissions_create' => 'Berechtigungen zum Erstellen von Regalen werden ausschließlich dazu verwendet, Berechtigungen mithilfe der unten beschriebenen Aktion auf untergeordnete Bücher zu kopieren. Sie haben keinen Einfluss auf die Möglichkeit, Bücher zu erstellen.', + 'shelves_copy_permissions_to_books' => 'Kopiere die Berechtigungen zu den Büchern', + 'shelves_copy_permissions' => 'Kopierrechte', + 'shelves_copy_permissions_explain' => 'Dadurch werden die aktuellen Berechtigungseinstellungen dieses Regals auf alle darin enthaltenen Bücher angewendet. Stellen Sie vor der Aktivierung sicher, dass alle Änderungen an den Berechtigungen dieses Regals gespeichert wurden.', + 'shelves_copy_permission_success' => 'Regal Berechtigungen auf :count Bücher kopiert', // Books 'book' => 'Buch', 'books' => 'Bücher', 'x_books' => ':count Buch|:count Bücher', - 'books_empty' => 'Keine Bücher vorhanden', + 'books_empty' => 'Es wurden keine Bücher erstellt', 'books_popular' => 'Beliebte Bücher', - 'books_recent' => 'Kürzlich angesehene Bücher', + 'books_recent' => 'Aktuelle Bücher', 'books_new' => 'Neue Bücher', 'books_new_action' => 'Neues Buch', 'books_popular_empty' => 'Die beliebtesten Bücher werden hier angezeigt.', - 'books_new_empty' => 'Die neusten Bücher werden hier angezeigt.', + 'books_new_empty' => 'Hier werden die zuletzt erstellten Bücher angezeigt.', 'books_create' => 'Neues Buch erstellen', 'books_delete' => 'Buch löschen', 'books_delete_named' => 'Buch ":bookName" löschen', - 'books_delete_explain' => 'Das Buch ":bookName" wird gelöscht und alle zugehörigen Kapitel und Seiten entfernt.', - 'books_delete_confirmation' => 'Sind Sie sicher, dass Sie dieses Buch löschen möchten?', + 'books_delete_explain' => 'Dadurch wird das Buch mit dem Namen „:bookName“ gelöscht. Alle Seiten und Kapitel werden entfernt. Das Buch ":bookName" wird gelöscht und alle zugehörigen Kapitel und Seiten entfernt.', + 'books_delete_confirmation' => 'Möchtest du dieses Buch wirklich löschen?', 'books_edit' => 'Buch bearbeiten', 'books_edit_named' => 'Buch ":bookName" bearbeiten', 'books_form_book_name' => 'Name des Buches', 'books_save' => 'Buch speichern', - 'books_permissions' => 'Buch-Berechtigungen', - 'books_permissions_updated' => 'Buch-Berechtigungen aktualisiert', - 'books_empty_contents' => 'Es sind noch keine Seiten oder Kapitel zu diesem Buch hinzugefügt worden.', - 'books_empty_create_page' => 'Neue Seite anlegen', + 'books_permissions' => 'Buch Berechtigungen', + 'books_permissions_updated' => 'Bücherberechtigungen aktualisiert', + 'books_empty_contents' => 'Für dieses Buch wurden noch keine Seiten oder Kapitel angelegt.', + 'books_empty_create_page' => 'Eine neue Seite erstellen', 'books_empty_sort_current_book' => 'Aktuelles Buch sortieren', - 'books_empty_add_chapter' => 'Neues Kapitel hinzufügen', - 'books_permissions_active' => 'Buch-Berechtigungen aktiv', - 'books_search_this' => 'Dieses Buch durchsuchen', + 'books_empty_add_chapter' => 'Ein Kapitel hinzufügen', + 'books_permissions_active' => 'Bücherberechtigungen aktiv', + 'books_search_this' => 'In diesem Buch suchen', 'books_navigation' => 'Buchnavigation', - 'books_sort' => 'Buchinhalte sortieren', - 'books_sort_desc' => 'Kapitel und Seiten innerhalb eines Buches verschieben, um dessen Inhalt zu reorganisieren. Andere Bücher können hinzugefügt werden, was das Verschieben von Kapiteln und Seiten zwischen Büchern erleichtert. Optional kann eine automatische Sortierregel erstellt werden, um den Inhalt dieses Buches nach Änderungen automatisch zu sortieren.', - 'books_sort_auto_sort' => 'Auto-Sortieroption', + 'books_sort' => 'Buchinhalt sortieren', + 'books_sort_desc' => 'Verschieben Sie Kapitel und Seiten innerhalb eines Buches, um dessen Inhalt neu zu ordnen. Es können weitere Bücher hinzugefügt werden, wodurch Kapitel und Seiten problemlos zwischen den Büchern verschoben werden können. Optional kann eine automatische Sortierregel festgelegt werden, um den Inhalt dieses Buches bei Änderungen automatisch zu sortieren.', + 'books_sort_auto_sort' => 'Automatische Sortierfunktionsoption', 'books_sort_auto_sort_active' => 'Automatische Sortierung aktiv: :sortName', 'books_sort_named' => 'Buch ":bookName" sortieren', 'books_sort_name' => 'Sortieren nach Namen', 'books_sort_created' => 'Sortieren nach Erstellungsdatum', 'books_sort_updated' => 'Sortieren nach Aktualisierungsdatum', - 'books_sort_chapters_first' => 'Kapitel zuerst', - 'books_sort_chapters_last' => 'Kapitel zuletzt', + 'books_sort_chapters_first' => 'Erstes Kapitel zuerst', + 'books_sort_chapters_last' => 'Letztes Kapitel zuletzt', 'books_sort_show_other' => 'Andere Bücher anzeigen', 'books_sort_save' => 'Neue Reihenfolge speichern', - 'books_sort_show_other_desc' => 'Füge hier weitere Bücher hinzu, um sie in die Sortierung einzubinden und ermögliche so eine einfache und übergreifende Reorganisation.', + 'books_sort_show_other_desc' => 'Fügen Sie hier weitere Bücher hinzu, um sie in die Sortierung einzubeziehen, und ermöglichen Sie so eine einfache Neuanordnung über mehrere Bücher hinweg.', 'books_sort_move_up' => 'Nach oben bewegen', 'books_sort_move_down' => 'Nach unten bewegen', - 'books_sort_move_prev_book' => 'Zum vorherigen Buch verschieben', - 'books_sort_move_next_book' => 'Zum nächsten Buch verschieben', - 'books_sort_move_prev_chapter' => 'In das vorherige Kapitel verschieben', - 'books_sort_move_next_chapter' => 'In nächstes Kapitel verschieben', - 'books_sort_move_book_start' => 'Zum Buchbeginn verschieben', - 'books_sort_move_book_end' => 'Zum Ende des Buches verschieben', - 'books_sort_move_before_chapter' => 'Vor Kapitel verschieben', - 'books_sort_move_after_chapter' => 'Nach Kapitel verschieben', + 'books_sort_move_prev_book' => 'Zum vorherigen Buch wechseln', + 'books_sort_move_next_book' => 'Zum nächsten Buch wechseln', + 'books_sort_move_prev_chapter' => 'Zum vorherigen Kapitel wechseln', + 'books_sort_move_next_chapter' => 'In das nächste Kapitel wechseln', + 'books_sort_move_book_start' => 'Zum Anfang des Buches springen', + 'books_sort_move_book_end' => 'Zum Ende des Buches springen', + 'books_sort_move_before_chapter' => 'Zum vorherigen Kapitel springen', + 'books_sort_move_after_chapter' => 'Weiter zum nächsten Kapitel', 'books_copy' => 'Buch kopieren', 'books_copy_success' => 'Das Buch wurde erfolgreich kopiert', @@ -201,11 +201,11 @@ 'x_chapters' => ':count Kapitel', 'chapters_popular' => 'Beliebte Kapitel', 'chapters_new' => 'Neues Kapitel', - 'chapters_create' => 'Neues Kapitel anlegen', - 'chapters_delete' => 'Kapitel entfernen', + 'chapters_create' => 'Neues Kapitel erstellen', + 'chapters_delete' => 'Kapitel löschen', 'chapters_delete_named' => 'Kapitel ":chapterName" entfernen', - 'chapters_delete_explain' => 'Dies löscht das Kapitel mit dem Namen \':chapterName\'. Alle Seiten, die innerhalb dieses Kapitels existieren, werden ebenfalls gelöscht.', - 'chapters_delete_confirm' => 'Sind Sie sicher, dass Sie dieses Kapitel löschen möchten?', + 'chapters_delete_explain' => 'Dadurch wird das Kapitel mit dem Namen „:chapterName“ gelöscht. Alle Seiten, die zu diesem Kapitel gehören, werden ebenfalls gelöscht.', + 'chapters_delete_confirm' => 'Möchtest du dieses Kapitel wirklich löschen?', 'chapters_edit' => 'Kapitel bearbeiten', 'chapters_edit_named' => 'Kapitel ":chapterName" bearbeiten', 'chapters_save' => 'Kapitel speichern', @@ -213,10 +213,10 @@ 'chapters_move_named' => 'Kapitel ":chapterName" verschieben', 'chapters_copy' => 'Kapitel kopieren', 'chapters_copy_success' => 'Kapitel erfolgreich kopiert', - 'chapters_permissions' => 'Kapitel-Berechtigungen', - 'chapters_empty' => 'Aktuell sind keine Kapitel diesem Buch hinzugefügt worden.', - 'chapters_permissions_active' => 'Kapitel-Berechtigungen aktiv', - 'chapters_permissions_success' => 'Kapitel-Berechtigungenen aktualisisert', + 'chapters_permissions' => 'Kapitel Berechtigungen', + 'chapters_empty' => 'Dieses Kapitel enthält derzeit keine Seiten.', + 'chapters_permissions_active' => 'Kapitel Berechtigungen aktiv', + 'chapters_permissions_success' => 'Kapitel Berechtigungen aktualisiert', 'chapters_search_this' => 'Dieses Kapitel durchsuchen', 'chapter_sort_book' => 'Buch sortieren', @@ -230,22 +230,22 @@ 'pages_navigation' => 'Seitennavigation', 'pages_delete' => 'Seite löschen', 'pages_delete_named' => 'Seite ":pageName" löschen', - 'pages_delete_draft_named' => 'Seitenentwurf von ":pageName" löschen', - 'pages_delete_draft' => 'Seitenentwurf löschen', + 'pages_delete_draft_named' => 'Entwurf von ":pageName" löschen', + 'pages_delete_draft' => 'Entwurf löschen', 'pages_delete_success' => 'Seite gelöscht', - 'pages_delete_draft_success' => 'Seitenentwurf gelöscht', - 'pages_delete_warning_template' => 'Diese Seite wird aktiv als Standardvorlage für Bücher oder Kapitel verwendet. In diesen Büchern oder Kapiteln wird nach dem Löschen dieser Seite keine Standardvorlage mehr zugewiesen sein.', - 'pages_delete_confirm' => 'Sind Sie sicher, dass Sie diese Seite löschen möchen?', - 'pages_delete_draft_confirm' => 'Sind Sie sicher, dass Sie diesen Seitenentwurf löschen möchten?', + 'pages_delete_draft_success' => 'Entwurf der Seite gelöscht', + 'pages_delete_warning_template' => 'Diese Seite wird derzeit als Standardvorlage für Bücher oder Kapitel verwendet. Nach dem Löschen dieser Seite wird diesen Büchern oder Kapiteln keine Standardvorlage mehr zugewiesen.', + 'pages_delete_confirm' => 'Möchtest du diese Seite wirklich löschen?', + 'pages_delete_draft_confirm' => 'Möchtest du diese Entwurfsseite wirklich löschen?', 'pages_editing_named' => 'Seite ":pageName" bearbeiten', 'pages_edit_draft_options' => 'Entwurfsoptionen', 'pages_edit_save_draft' => 'Entwurf speichern', 'pages_edit_draft' => 'Seitenentwurf bearbeiten', - 'pages_editing_draft' => 'Seitenentwurf bearbeiten', + 'pages_editing_draft' => 'Entwurf bearbeiten', 'pages_editing_page' => 'Seite bearbeiten', - 'pages_edit_draft_save_at' => 'Entwurf gesichert um ', + 'pages_edit_draft_save_at' => 'Entwurf gespeichert unter ', 'pages_edit_delete_draft' => 'Entwurf löschen', - 'pages_edit_delete_draft_confirm' => 'Sind Sie sicher, dass Sie Ihren Entwurf löschen möchten? Alle Ihre Änderungen seit dem letzten vollständigen Speichern gehen verloren und der Editor wird mit dem letzten Speicherzustand aktualisiert, der kein Entwurf ist.', + 'pages_edit_delete_draft_confirm' => 'Möchten Sie die Änderungen an Ihrem Seitenentwurf wirklich löschen? Alle Ihre Änderungen seit der letzten vollständigen Speicherung gehen verloren, und der Editor wird mit dem letzten gespeicherten Stand der Seite, ohne Entwurf, aktualisiert.', 'pages_edit_discard_draft' => 'Entwurf verwerfen', 'pages_edit_switch_to_markdown' => 'Zum Markdown-Editor wechseln', 'pages_edit_switch_to_markdown_clean' => '(Gesäuberter Inhalt)', @@ -254,18 +254,18 @@ 'pages_edit_switch_to_new_wysiwyg' => 'Zum neuen WYSIWYG wechseln', 'pages_edit_switch_to_new_wysiwyg_desc' => '(Im Beta-Test)', 'pages_edit_set_changelog' => 'Änderungsprotokoll hinzufügen', - 'pages_edit_enter_changelog_desc' => 'Bitte geben Sie eine kurze Zusammenfassung Ihrer Änderungen ein', + 'pages_edit_enter_changelog_desc' => 'Geben Sie eine kurze Beschreibung der vorgenommenen Änderungen ein', 'pages_edit_enter_changelog' => 'Änderungsprotokoll eingeben', 'pages_editor_switch_title' => 'Editor wechseln', - 'pages_editor_switch_are_you_sure' => 'Sind Sie sicher, dass Sie den Editor für diese Seite ändern möchten?', - 'pages_editor_switch_consider_following' => 'Betrachten Sie folgendes beim Ändern von Editoren:', - 'pages_editor_switch_consideration_a' => 'Einmal gespeichert, wird die neue Editoroption von zukünftigen Editoren verwendet, einschließlich derjenigen, die nicht in der Lage sind, den Editortyp selbst zu ändern.', - 'pages_editor_switch_consideration_b' => 'Dies kann unter bestimmten Umständen zu einem Verlust von Details und Quellcode führen.', - 'pages_editor_switch_consideration_c' => 'Änderungen des Tags oder Änderungsprotokolls, die seit dem letzten Speichern vorgenommen wurden, werden bei dieser Änderung nicht fortgesetzt.', + 'pages_editor_switch_are_you_sure' => 'Möchtest du den Editor dieser Seite wirklich ändern?', + 'pages_editor_switch_consider_following' => 'Beachten Sie beim Wechsel des Editors Folgendes:', + 'pages_editor_switch_consideration_a' => 'Sobald die Einstellung gespeichert ist, wird die neue Editor-Option von allen zukünftigen Editoren verwendet, auch von solchen, die den Editor-Typ möglicherweise nicht selbst ändern können.', + 'pages_editor_switch_consideration_b' => 'Dies kann unter bestimmten Umständen zu einem Verlust an Details und Syntax führen.', + 'pages_editor_switch_consideration_c' => 'Änderungen an Tags oder im Änderungsprotokoll, die seit dem letzten Speichern vorgenommen wurden, bleiben bei dieser Änderung nicht erhalten.', 'pages_save' => 'Seite speichern', 'pages_title' => 'Seitentitel', 'pages_name' => 'Seitenname', - 'pages_md_editor' => 'Redakteur', + 'pages_md_editor' => 'Editor', 'pages_md_preview' => 'Vorschau', 'pages_md_insert_image' => 'Bild einfügen', 'pages_md_insert_link' => 'Link zu einem Objekt einfügen', @@ -273,18 +273,18 @@ 'pages_md_show_preview' => 'Vorschau anzeigen', 'pages_md_sync_scroll' => 'Vorschau synchronisieren', 'pages_md_plain_editor' => 'Einfacher Editor', - 'pages_drawing_unsaved' => 'Ungespeicherte Zeichnung gefunden', - 'pages_drawing_unsaved_confirm' => 'Es wurden ungespeicherte Zeichnungsdaten von einem früheren, fehlgeschlagenen Versuch, die Zeichnung zu speichern, gefunden. Möchten Sie diese ungespeicherte Zeichnung wiederherstellen und weiter bearbeiten?', - 'pages_not_in_chapter' => 'Seite ist in keinem Kapitel', + 'pages_drawing_unsaved' => 'Nicht gespeicherte Zeichnung gefunden', + 'pages_drawing_unsaved_confirm' => 'Es wurden nicht gespeicherte Zeichnungsdaten aus einem früheren fehlgeschlagenen Speichervorgang gefunden. Möchten Sie diese nicht gespeicherte Zeichnung wiederherstellen und weiter bearbeiten?', + 'pages_not_in_chapter' => 'Die Seite gehört zu keinem Kapitel', 'pages_move' => 'Seite verschieben', 'pages_copy' => 'Seite kopieren', - 'pages_copy_desination' => 'Ziel', + 'pages_copy_desination' => 'Kopierziel', 'pages_copy_success' => 'Seite erfolgreich kopiert', 'pages_permissions' => 'Seiten Berechtigungen', 'pages_permissions_success' => 'Seiten Berechtigungen aktualisiert', - 'pages_revision' => 'Version', - 'pages_revisions' => 'Seitenversionen', - 'pages_revisions_desc' => 'Alle vorherhigen Revisionen dieser Seite sind unten aufgelistet. Sie können zurückschauen, vergleichen und alte Seitenversionen wiederherstellen, wenn die Berechtigungen dies erlauben. Der vollständige Verlauf der Seite kann hier möglicherweise nicht vollständig wiedergegeben werden, da je nach Systemkonfiguration alte Revisionen automatisch hätten gelöscht werden können.', + 'pages_revision' => 'Überarbeitung', + 'pages_revisions' => 'Seitenüberarbeitungen', + 'pages_revisions_desc' => 'Nachfolgend sind alle bisherigen Überarbeitungen dieser Seite aufgeführt. Sofern Sie über die entsprechenden Berechtigungen verfügen, können Sie alte Seitenversionen einsehen, vergleichen und wiederherstellen. Möglicherweise ist der vollständige Verlauf der Seite hier nicht vollständig dargestellt, da alte Überarbeitungen je nach Systemkonfiguration automatisch gelöscht werden können.', 'pages_revisions_named' => 'Seitenversionen von ":pageName"', 'pages_revision_named' => 'Seitenversion von ":pageName"', 'pages_revision_restored_from' => 'Wiederhergestellt von #:id; :summary', diff --git a/lang/de/settings.php b/lang/de/settings.php index e57ac52c457..64af973a46f 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -17,143 +17,142 @@ 'app_features_security' => 'Funktionen & Sicherheit', 'app_name' => 'Anwendungsname', 'app_name_desc' => 'Dieser Name wird im Header und in E-Mails angezeigt.', - 'app_name_header' => 'Anwendungsname im Header anzeigen?', + 'app_name_header' => 'Namen in der Kopfzeile anzeigen', 'app_public_access' => 'Öffentlicher Zugriff', - 'app_public_access_desc' => 'Wenn Sie diese Option aktivieren, können Besucher, die nicht angemeldet sind, auf Inhalte in Ihrer BookStack-Instanz zugreifen.', - 'app_public_access_desc_guest' => 'Der Zugang für öffentliche Besucher kann über den Benutzer "Guest" gesteuert werden.', - 'app_public_access_toggle' => 'Öffentlichen Zugriff erlauben', - 'app_public_viewing' => 'Öffentliche Ansicht erlauben?', - 'app_secure_images' => 'Erhöhte Sicherheit für hochgeladene Bilder aktivieren?', + 'app_public_access_desc' => 'Wenn Sie diese Option aktivieren, können Besucher, die nicht angemeldet sind, auf Inhalte in Ihrer BookStack Instanz zugreifen.', + 'app_public_access_desc_guest' => 'Der Zugang für externe Besucher kann über den Benutzer „Gast“ geregelt werden.', + 'app_public_access_toggle' => 'Öffentlichen Zugriff gewähren', + 'app_public_viewing' => 'Öffentlich zugänglich machen?', + 'app_secure_images' => 'Sichereres Hochladen von Bildern', 'app_secure_images_toggle' => 'Höhere Sicherheit für Bild-Uploads aktivieren', - 'app_secure_images_desc' => 'Aus Leistungsgründen sind alle Bilder öffentlich sichtbar. Diese Option fügt zufällige, schwer zu erratende, Zeichenketten zu Bild-URLs hinzu. Stellen Sie sicher, dass Verzeichnisindizes deaktiviert sind, um einen einfachen Zugriff zu verhindern.', + 'app_secure_images_desc' => 'Aus Leistungsgründen sind alle Bilder öffentlich zugänglich. Diese Option fügt den Bild-URLs eine zufällige, schwer zu erratende Zeichenfolge vor. Stellen Sie sicher, dass Verzeichnisverzeichnisse nicht aktiviert sind, um einen einfachen Zugriff zu verhindern.', 'app_default_editor' => 'Standard-Seiten-Editor', - 'app_default_editor_desc' => 'Wählen Sie aus, welcher Editor standardmäßig beim Bearbeiten neuer Seiten verwendet wird. Dies kann auf einer Seitenebene überschrieben werden, wenn es die Berechtigungen erlauben.', + 'app_default_editor_desc' => 'Wählen Sie aus, welcher Editor standardmäßig beim Bearbeiten neuer Seiten verwendet werden soll. Diese Einstellung kann auf Seitenebene überschrieben werden, sofern die Berechtigungen dies zulassen.', 'app_custom_html' => 'Benutzerdefinierter HTML-Head-Inhalt', - 'app_custom_html_desc' => 'Jeder Inhalt, der hier hinzugefügt wird, wird am Ende der -Sektion jeder Seite eingefügt. Diese kann praktisch sein, um CSS-Styles anzupassen oder Analytics-Code hinzuzufügen.', - 'app_custom_html_disabled_notice' => 'Benutzerdefinierte HTML-Kopfzeileninhalte sind auf dieser Einstellungsseite deaktiviert, um sicherzustellen, dass alle Änderungen rückgängig gemacht werden können.', + 'app_custom_html_desc' => 'Alle hier hinzugefügten Inhalte werden am Ende des -Abschnitts jeder Seite eingefügt. Dies ist nützlich, um Stile zu überschreiben oder Analysecodes hinzuzufügen.', + 'app_custom_html_disabled_notice' => 'Auf dieser Einstellungsseite ist der benutzerdefinierte HTML-Head-Inhalt deaktiviert, um sicherzustellen, dass etwaige grundlegende Änderungen rückgängig gemacht werden können.', 'app_logo' => 'Anwendungslogo', - 'app_logo_desc' => 'Dies wird unter anderem in der Kopfzeile der Anwendung verwendet. Dieses Bild sollte 86px hoch sein. Große Bilder werden herunterskaliert.', + 'app_logo_desc' => 'Dieses Bild wird unter anderem in der Kopfzeile der Anwendung verwendet. Es sollte eine Höhe von 86 Pixel haben. Größere Bilder werden verkleinert.', 'app_icon' => 'Anwendungssymbol', - 'app_icon_desc' => 'Dieses Symbol wird für Browser-Registerkarten und Verknüpfungssymbole verwendet. Dies sollte ein 256px quadratisches PNG-Bild sein.', + 'app_icon_desc' => 'Dieses Symbol wird für Browser-Registerkarten und Verknüpfungssymbole verwendet. Es sollte sich um ein quadratisches PNG-Bild mit einer Größe von 256 px handeln.', 'app_homepage' => 'Startseite der Anwendung', - 'app_homepage_desc' => 'Wählen Sie eine Seite als Startseite aus, die statt der Standardansicht angezeigt werden soll. Seitenberechtigungen werden für die ausgewählten Seiten ignoriert.', + 'app_homepage_desc' => 'Wählen Sie eine Ansicht aus, die anstelle der Standardansicht auf der Startseite angezeigt werden soll. Seitenberechtigungen werden für ausgewählte Seiten ignoriert.', 'app_homepage_select' => 'Wählen Sie eine Seite aus', - 'app_footer_links' => 'Fußzeilen-Links', - 'app_footer_links_desc' => 'Fügen Sie Links hinzu, die innerhalb der Seitenfußzeile angezeigt werden. Diese werden am unteren Ende der meisten Seiten angezeigt, einschließlich derjenigen, die keine Anmeldung benötigen. Sie können die Bezeichnung "trans::" verwenden, um systemdefinierte Übersetzungen zu verwenden. Beispiel: Mit "trans::common.privacy_policy" wird der übersetzte Text "Privacy Policy" bereitgestellt und "trans::common.terms_of_service" liefert den übersetzten Text "Terms of Service".', + 'app_footer_links' => 'Links in der Fußzeile', + 'app_footer_links_desc' => 'Fügen Sie Links hinzu, die in der Fußzeile der Website angezeigt werden sollen. Diese werden am Ende der meisten Seiten angezeigt, auch auf solchen, für die keine Anmeldung erforderlich ist. Sie können die Bezeichnung „trans::“ verwenden, um vom System definierte Übersetzungen zu nutzen. Beispiel: Die Verwendung von „trans::common.privacy_policy“ liefert den übersetzten Text „Datenschutzerklärung“ und „trans::common.terms_of_service“ liefert den übersetzten Text „Nutzungsbedingungen“.', 'app_footer_links_label' => 'Link-Label', 'app_footer_links_url' => 'Link-URL', 'app_footer_links_add' => 'Fußzeilen-Link hinzufügen', 'app_disable_comments' => 'Kommentare deaktivieren', 'app_disable_comments_toggle' => 'Kommentare deaktivieren', - 'app_disable_comments_desc' => 'Deaktiviert Kommentare über alle Seiten in der Anwendung. Vorhandene Kommentare werden nicht angezeigt.', + 'app_disable_comments_desc' => 'Deaktiviert Kommentare auf allen Seiten der Anwendung. Bereits vorhandene Kommentare werden nicht angezeigt.', // Color settings 'color_scheme' => 'Farbschema der Anwendung', 'color_scheme_desc' => 'Lege die Farben, die in der Benutzeroberfläche verwendet werden, fest. Farben können separat für dunkle und helle Modi konfiguriert werden, um am besten zum Farbschema zu passen und die Lesbarkeit zu gewährleisten.', - 'ui_colors_desc' => 'Lege die primäre Farbe und die Standard-Linkfarbe der Anwendung fest. Die primäre Farbe wird hauptsächlich für Kopfzeilen, Buttons und Interface-Dekorationen verwendet. Die Standard-Linkfarbe wird für textbasierte Links und Aktionen sowohl innerhalb des geschriebenen Inhalts als auch in der Benutzeroberfläche verwendet.', + 'ui_colors_desc' => 'Legen Sie die Hauptfarbe der Anwendung und die Standardfarbe für Links fest. Die Hauptfarbe wird hauptsächlich für das Kopfzeilenbanner, Schaltflächen und Elemente zur Gestaltung der Benutzeroberfläche verwendet. Die Standardfarbe für Links wird für textbasierte Links und Aktionen verwendet, sowohl innerhalb von Textinhalten als auch in der Benutzeroberfläche der Anwendung.', 'app_color' => 'Primäre Farbe', - 'link_color' => 'Standard-Linkfarbe', + 'link_color' => 'Standardfarbe für Links', 'content_colors_desc' => 'Legt Farben für alle Elemente in der Seitenorganisationshierarchie fest. Die Auswahl von Farben mit einer ähnlichen Helligkeit wie die Standardfarben wird zur Lesbarkeit empfohlen.', - 'bookshelf_color' => 'Regalfarbe', + 'bookshelf_color' => 'Farbe des Regals', 'book_color' => 'Buchfarbe', - 'chapter_color' => 'Kapitelfarbe', + 'chapter_color' => 'Kapitel-Farbe', 'page_color' => 'Seitenfarbe', - 'page_draft_color' => 'Seitenentwurfsfarbe', + 'page_draft_color' => 'Farbe des Seitenentwurfs', // Registration Settings - 'reg_settings' => 'Registrierungseinstellungen', + 'reg_settings' => 'Registrierung', 'reg_enable' => 'Registrierung erlauben', 'reg_enable_toggle' => 'Registrierung erlauben', - 'reg_enable_desc' => 'Wenn die Registrierung erlaubt ist, kann sich der Benutzer als Anwendungsbenutzer anmelden. Bei der Registrierung erhält er eine einzige, voreingestellte Benutzerrolle.', - 'reg_default_role' => 'Standard-Benutzerrolle nach Registrierung', - 'reg_enable_external_warning' => 'Die obige Option wird ignoriert, während eine externe LDAP oder SAML Authentifizierung aktiv ist. Benutzerkonten für nicht existierende Mitglieder werden automatisch erzeugt, wenn die Authentifizierung gegen das verwendete externe System erfolgreich ist.', - 'reg_email_confirmation' => 'Bestätigung per E-Mail', - 'reg_email_confirmation_toggle' => 'Bestätigung per E-Mail erforderlich', + 'reg_enable_desc' => 'Wenn die Registrierung aktiviert ist, können sich Benutzer als Anwendungsbenutzer registrieren. Bei der Registrierung erhalten sie eine einzige Standardbenutzerrolle.', + 'reg_default_role' => 'Standardbenutzerrolle nach der Registrierung', + 'reg_enable_external_warning' => 'Die oben genannte Option wird ignoriert, solange die externe LDAP oder SAML Authentifizierung aktiv ist. Benutzerkonten für nicht vorhandene Mitglieder werden automatisch angelegt, wenn die Authentifizierung gegenüber dem verwendeten externen System erfolgreich ist.', + 'reg_email_confirmation' => 'E-Mail-Bestätigung', + 'reg_email_confirmation_toggle' => 'E-Mail-Bestätigung erforderlich', 'reg_confirm_email_desc' => 'Falls die Einschränkung für Domains genutzt wird, ist die Bestätigung per E-Mail zwingend erforderlich und der untenstehende Wert wird ignoriert.', 'reg_confirm_restrict_domain' => 'Registrierung auf bestimmte Domains einschränken', - 'reg_confirm_restrict_domain_desc' => 'Fügen Sie eine durch Komma getrennte Liste von Domains hinzu, auf die die Registrierung eingeschränkt werden soll. Benutzern wird eine E-Mail gesendet, um ihre E-Mail-Adresse zu bestätigen, bevor diese die Anwendung nutzen können. -Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung ändern.', + 'reg_confirm_restrict_domain_desc' => 'Geben Sie eine durch Kommas getrennte Liste der E-Mail-Domains ein, auf die Sie die Registrierung beschränken möchten. Die Nutzer erhalten eine E-Mail zur Bestätigung ihrer Adresse, bevor sie die Anwendung nutzen dürfen. Beachten Sie, dass die Nutzer ihre E-Mail-Adressen nach erfolgreicher Registrierung ändern können.', 'reg_confirm_restrict_domain_placeholder' => 'Keine Einschränkung gesetzt', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', + 'sorting' => 'Listen & Sortieren', + 'sorting_book_default' => 'Standardregel für die Sortierung von Büchern', 'sorting_book_default_desc' => 'Wählen Sie die Standard-Sortierregel aus, die auf neue Bücher angewendet werden soll. Dies wirkt sich nicht auf bestehende Bücher aus und kann pro Buch überschrieben werden.', 'sorting_rules' => 'Sortierregeln', 'sorting_rules_desc' => 'Dies sind vordefinierte Sortieraktionen, die auf Inhalte im System angewendet werden können.', - 'sort_rule_assigned_to_x_books' => ':count Buch zugewiesen|:count Büchern zugewiesen', + 'sort_rule_assigned_to_x_books' => 'Zugewiesen an: :count Buch|zugewiesen an: :count Bücher', 'sort_rule_create' => 'Sortierregel erstellen', 'sort_rule_edit' => 'Sortierregel bearbeiten', 'sort_rule_delete' => 'Sortierregel löschen', 'sort_rule_delete_desc' => 'Diese Sortierregel aus dem System entfernen. Bücher mit dieser Sortierung werden auf manuelle Sortierung zurückgesetzt.', - 'sort_rule_delete_warn_books' => 'Diese Sortierregel wird derzeit in :count Bücher(n) verwendet. Sind Sie sicher, dass Sie dies löschen möchten?', - 'sort_rule_delete_warn_default' => 'Diese Sortierregel wird derzeit als Standard für Bücher verwendet. Sind Sie sicher, dass Sie dies löschen möchten?', + 'sort_rule_delete_warn_books' => 'Diese Sortierregel wird derzeit auf :count Bücher angewendet. Möchten Sie diese wirklich löschen?', + 'sort_rule_delete_warn_default' => 'Diese Sortierregel wird derzeit Standard mäßig für Bücher verwendet. Möchten Sie sie wirklich löschen?', 'sort_rule_details' => 'Sortierregel-Details', - 'sort_rule_details_desc' => 'Legen Sie einen Namen für diese Sortierregel fest, der in Listen erscheint, wenn Benutzer eine Sortierung auswählen.', - 'sort_rule_operations' => 'Sortierungs-Aktionen', - 'sort_rule_operations_desc' => 'Konfigurieren Sie die durchzuführenden Sortieraktionen durch Verschieben von der Liste der verfügbaren Aktionen. Bei der Verwendung werden die Aktionen von oben nach unten angewendet. Alle hier vorgenommenen Änderungen werden beim Speichern auf alle zugewiesenen Bücher angewendet.', - 'sort_rule_available_operations' => 'Verfügbare Aktionen', - 'sort_rule_available_operations_empty' => 'Keine verbleibenden Aktionen', - 'sort_rule_configured_operations' => 'Konfigurierte Aktionen', - 'sort_rule_configured_operations_empty' => 'Aktionen aus der Liste "Verfügbare Operationen" ziehen/hinzufügen', - 'sort_rule_op_asc' => '(Aufst.)', - 'sort_rule_op_desc' => '(Abst.)', + 'sort_rule_details_desc' => 'Geben Sie einen Namen für diese Sortierregel ein, der in Listen angezeigt wird, wenn der Benutzer eine Sortieroption auswählt.', + 'sort_rule_operations' => 'Sortiervorgänge', + 'sort_rule_operations_desc' => 'Konfigurieren Sie die auszuführenden Sortieraktionen, indem Sie sie aus der Liste der verfügbaren Vorgänge verschieben. Bei der Ausführung werden die Vorgänge der Reihe nach von oben nach unten angewendet. Alle hier vorgenommenen Änderungen werden beim Speichern auf alle zugewiesenen Bücher angewendet.', + 'sort_rule_available_operations' => 'Verfügbare Funktionen', + 'sort_rule_available_operations_empty' => 'Es sind keine Vorgänge mehr vorhanden', + 'sort_rule_configured_operations' => 'Konfigurierte Vorgänge', + 'sort_rule_configured_operations_empty' => 'Vorgänge aus der Liste „Verfügbare Vorgänge“ per Drag-and-drop hinzufügen', + 'sort_rule_op_asc' => '(Aufsteigend)', + 'sort_rule_op_desc' => '(Absteigend)', 'sort_rule_op_name' => 'Name - Alphabetisch', 'sort_rule_op_name_numeric' => 'Name - Numerisch', 'sort_rule_op_created_date' => 'Erstellungsdatum', 'sort_rule_op_updated_date' => 'Aktualisierungsdatum', 'sort_rule_op_chapters_first' => 'Kapitel zuerst', 'sort_rule_op_chapters_last' => 'Kapitel zuletzt', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sorting_page_limits' => 'Anzeigebegrenzungen pro Seite', + 'sorting_page_limits_desc' => 'Legen Sie fest, wie viele Elemente pro Seite in den verschiedenen Listen des Systems angezeigt werden sollen. In der Regel ist eine geringere Anzahl leistungsfähiger, während eine höhere Anzahl das Blättern durch mehrere Seiten überflüssig macht. Es wird empfohlen, ein Vielfaches von 6 zu verwenden.', // Maintenance settings 'maint' => 'Wartung', 'maint_image_cleanup' => 'Bilder bereinigen', - 'maint_image_cleanup_desc' => 'Überprüft Seiten- und Versionsinhalte auf ungenutzte und mehrfach vorhandene Bilder. Erstellen Sie vor dem Start ein Backup Ihrer Datenbank und Bilder.', - 'maint_delete_images_only_in_revisions' => 'Lösche auch Bilder, die nur in alten Seitenüberarbeitungen vorhanden sind', + 'maint_image_cleanup_desc' => 'Überprüft den Inhalt der Seiten und Versionen, um festzustellen, welche Bilder und Zeichnungen derzeit verwendet werden und welche Bilder überflüssig sind. Stellen Sie sicher, dass Sie vor der Ausführung eine vollständige Sicherung der Datenbank und der Bilder erstellen.', + 'maint_delete_images_only_in_revisions' => 'Lösche auch Bilder, die nur in alten Seitenversionen vorhanden sind', 'maint_image_cleanup_run' => 'Reinigung starten', - 'maint_image_cleanup_warning' => ':count eventuell unbenutze Bilder wurden gefunden. Möchten Sie diese Bilder löschen?', - 'maint_image_cleanup_success' => ':count eventuell unbenutze Bilder wurden gefunden und gelöscht.', - 'maint_image_cleanup_nothing_found' => 'Keine unbenutzen Bilder gefunden. Nichts zu löschen!', - 'maint_send_test_email' => 'Eine Test-E-Mail versenden', - 'maint_send_test_email_desc' => 'Dies sendet eine Test-E-Mail an Ihre in Ihrem Profil angegebene E-Mail-Adresse.', + 'maint_image_cleanup_warning' => 'Es wurden :count möglicherweise nicht mehr benötigte Bilder gefunden. Möchten Sie diese Bilder wirklich löschen?', + 'maint_image_cleanup_success' => ':count möglicherweise nicht verwendete Bilder wurden gefunden und gelöscht!', + 'maint_image_cleanup_nothing_found' => 'Es wurden keine ungenutzten Bilder gefunden, nichts wurde gelöscht!', + 'maint_send_test_email' => 'Test-E-Mail senden', + 'maint_send_test_email_desc' => 'Dadurch wird eine Test-E-Mail an die in Ihrem Profil angegebene E-Mail-Adresse gesendet.', 'maint_send_test_email_run' => 'Test-E-Mail senden', - 'maint_send_test_email_success' => 'E-Mail wurde an :address gesendet', + 'maint_send_test_email_success' => 'E-Mail gesendet an :address', 'maint_send_test_email_mail_subject' => 'Test-E-Mail', - 'maint_send_test_email_mail_greeting' => 'E-Mail-Versand scheint zu funktionieren!', - 'maint_send_test_email_mail_text' => 'Glückwunsch! Da Sie diese E-Mail Benachrichtigung erhalten haben, scheinen Ihre E-Mail-Einstellungen korrekt konfiguriert zu sein.', - 'maint_recycle_bin_desc' => 'Gelöschte Regale, Bücher, Kapitel & Seiten werden in den Papierkorb verschoben, so dass sie wiederhergestellt oder dauerhaft gelöscht werden können. Ältere Gegenstände im Papierkorb können, in Abhängigkeit von der Systemkonfiguration, nach einer Weile automatisch entfernt werden.', + 'maint_send_test_email_mail_greeting' => 'Die E-Mail-Zustellung scheint zu funktionieren!', + 'maint_send_test_email_mail_text' => 'Herzlichen Glückwunsch! Da Sie diese E-Mail-Benachrichtigung erhalten haben, scheinen Ihre E-Mail-Einstellungen korrekt konfiguriert zu sein.', + 'maint_recycle_bin_desc' => 'Gelöschte Regale, Bücher, Kapitel und Seiten werden in den Papierkorb verschoben, sodass sie wiederhergestellt oder endgültig gelöscht werden können. Ältere Elemente im Papierkorb werden je nach Systemkonfiguration nach einer gewissen Zeit möglicherweise automatisch entfernt.', 'maint_recycle_bin_open' => 'Papierkorb öffnen', 'maint_regen_references' => 'Referenzen neu generieren', - 'maint_regen_references_desc' => 'Diese Aktion wird den Referenzindex innerhalb der Datenbank neu erstellen. Dies wird normalerweise automatisch ausgeführt, aber diese Aktion kann nützlich sein, um alte Inhalte oder Inhalte zu indizieren, die mittels inoffizieller Methoden hinzugefügt wurden.', - 'maint_regen_references_success' => 'Referenz-Index wurde neu generiert!', - 'maint_timeout_command_note' => 'Hinweis: Die Ausführung dieser Aktion kann einige Zeit in Anspruch nehmen, was in einigen Webumgebungen zu Timeout-Problemen führen kann. Alternativ kann diese Aktion auch mit einem Terminalbefehl ausgeführt werden.', + 'maint_regen_references_desc' => 'Diese Aktion erstellt den Index für die Querverweise innerhalb der Datenbank neu. Dies geschieht normalerweise automatisch, doch diese Aktion kann nützlich sein, um ältere Inhalte oder Inhalte, die über inoffizielle Methoden hinzugefügt wurden, zu indizieren.', + 'maint_regen_references_success' => 'Der Referenzindex wurde neu generiert!', + 'maint_timeout_command_note' => 'Hinweis: Die Ausführung dieser Aktion kann einige Zeit in Anspruch nehmen, was in manchen Webumgebungen zu Zeitüberschreitungsfehlern führen kann. Alternativ kann diese Aktion über einen Terminal Befehl ausgeführt werden.', // Recycle Bin 'recycle_bin' => 'Papierkorb', - 'recycle_bin_desc' => 'Hier können Sie gelöschte Elemente wiederherstellen oder sie dauerhaft aus dem System entfernen. Diese Liste ist nicht gefiltert, im Gegensatz zu ähnlichen Aktivitätslisten im System, wo Berechtigungsfilter angewendet werden.', - 'recycle_bin_deleted_item' => 'Gelöschtes Element', + 'recycle_bin_desc' => 'Hier können Sie gelöschte Elemente wiederherstellen oder sie endgültig aus dem System entfernen. Diese Liste ist ungefiltert, im Gegensatz zu ähnlichen Aktivitätslisten im System, bei denen Berechtigungsfilter angewendet werden.', + 'recycle_bin_deleted_item' => 'Gelöschter Eintrag', 'recycle_bin_deleted_parent' => 'Übergeordnet', 'recycle_bin_deleted_by' => 'Gelöscht von', 'recycle_bin_deleted_at' => 'Löschzeitpunkt', - 'recycle_bin_permanently_delete' => 'Dauerhaft löschen', + 'recycle_bin_permanently_delete' => 'Endgültig löschen', 'recycle_bin_restore' => 'Wiederherstellen', 'recycle_bin_contents_empty' => 'Der Papierkorb ist derzeit leer', 'recycle_bin_empty' => 'Papierkorb leeren', - 'recycle_bin_empty_confirm' => 'Dies wird alle Gegenstände im Papierkorb dauerhaft entfernen, einschließlich der Inhalte, die darin enthalten sind. Sind Sie sicher, dass Sie den Papierkorb leeren möchten?', - 'recycle_bin_destroy_confirm' => 'Diese Aktion löscht dieses Element dauerhaft aus dem System, zusammen mit allen unten aufgeführten untergeordneten Elementen, und es ist nicht möglich, diesen Inhalt wiederherzustellen. Sind Sie sicher, dass Sie dieses Element dauerhaft löschen möchten?', + 'recycle_bin_empty_confirm' => 'Dadurch werden alle Elemente im Papierkorb sowie deren Inhalte endgültig gelöscht. Möchten Sie den Papierkorb wirklich leeren?', + 'recycle_bin_destroy_confirm' => 'Durch diesen Vorgang wird dieses Element zusammen mit allen unten aufgeführten untergeordneten Elementen endgültig aus dem System gelöscht, und Sie können diesen Inhalt nicht wiederherstellen. Möchten Sie dieses Element wirklich endgültig löschen?', 'recycle_bin_destroy_list' => 'Zu löschende Elemente', 'recycle_bin_restore_list' => 'Zu wiederherzustellende Elemente', - 'recycle_bin_restore_confirm' => 'Mit dieser Aktion wird das gelöschte Element einschließlich aller untergeordneten Elemente an seinen ursprünglichen Ort wiederherstellen. Wenn der ursprüngliche Ort gelöscht wurde und sich nun im Papierkorb befindet, muss auch das übergeordnete Element wiederhergestellt werden.', - 'recycle_bin_restore_deleted_parent' => 'Das übergeordnete Elements wurde ebenfalls gelöscht. Dieses Element wird weiterhin als gelöscht zählen, bis auch das übergeordnete Element wiederhergestellt wurde.', + 'recycle_bin_restore_confirm' => 'Durch diese Aktion wird das gelöschte Element einschließlich aller untergeordneten Elemente an seinem ursprünglichen Speicherort wiederhergestellt. Sollte der ursprüngliche Speicherort inzwischen gelöscht worden sein und sich nun im Papierkorb befinden, muss auch das übergeordnete Element wiederhergestellt werden.', + 'recycle_bin_restore_deleted_parent' => 'Das übergeordnete Element dieses Eintrags wurde ebenfalls gelöscht. Diese Einträge bleiben gelöscht, bis auch das übergeordnete Element wiederhergestellt wird.', 'recycle_bin_restore_parent' => 'Übergeordneter Eintrag wiederherstellen', - 'recycle_bin_destroy_notification' => ':count Elemente wurden aus dem Papierkorb gelöscht.', - 'recycle_bin_restore_notification' => ':count Elemente wurden aus dem Papierkorb wiederhergestellt.', + 'recycle_bin_destroy_notification' => 'Löscht :count Elemente aus dem Papierkorb.', + 'recycle_bin_restore_notification' => 'Es wurden :count der Elemente aus dem Papierkorb wiederhergestellt.', // Audit Log - 'audit' => 'Audit-Protokoll', - 'audit_desc' => 'Dieses Audit-Protokoll zeigt eine Liste der Aktivitäten an, welche vom System protokolliert werden. Im Gegensatz zu den anderen Aktivitätslisten im System, bei denen Berechtigungen angewendet werden, ist diese Liste ungefiltert.', + 'audit' => 'Prüfprotokoll', + 'audit_desc' => 'Dieses Protokoll zeigt eine Liste der im System erfassten Aktivitäten an. Im Gegensatz zu ähnlichen Aktivitätslisten im System, bei denen Berechtigungsfilter angewendet werden, ist diese Liste ungefiltert.', 'audit_event_filter' => 'Ereignisfilter', 'audit_event_filter_no_filter' => 'Kein Filter', 'audit_deleted_item' => 'Gelöschtes Objekt', @@ -169,133 +168,133 @@ // Role Settings 'roles' => 'Rollen', 'role_user_roles' => 'Benutzer-Rollen', - 'roles_index_desc' => 'Rollen werden verwendet, um Benutzer zu gruppieren System-Berechtigung für ihre Mitglieder zuzuweisen. Wenn ein Benutzer Mitglied mehrerer Rollen ist, stapeln die gewährten Berechtigungen und der Benutzer wird alle Fähigkeiten erben.', + 'roles_index_desc' => 'Rollen dienen dazu, Benutzer zu gruppieren und ihren Mitgliedern Systemberechtigungen zu erteilen. Wenn ein Benutzer Mitglied mehrerer Rollen ist, addieren sich die gewährten Berechtigungen, und der Benutzer erhält alle entsprechenden Befugnisse.', 'roles_x_users_assigned' => ':count Benutzer zugewiesen|:count Benutzer zugewiesen', 'roles_x_permissions_provided' => ':count Berechtigung|:count Berechtigungen', 'roles_assigned_users' => 'Zugewiesene Benutzer', - 'roles_permissions_provided' => 'Genutzte Berechtigungen', - 'role_create' => 'Neue Rolle anlegen', + 'roles_permissions_provided' => 'Verfügbare Berechtigungen', + 'role_create' => 'Neue Rolle erstellen', 'role_delete' => 'Rolle löschen', - 'role_delete_confirm' => 'Sie möchten die Rolle ":roleName" löschen.', - 'role_delete_users_assigned' => 'Diese Rolle ist :userCount Benutzern zugeordnet. Sie können unten eine neue Rolle auswählen, die Sie diesen Benutzern zuordnen möchten.', + 'role_delete_confirm' => 'Dadurch wird die Rolle mit dem Namen „:roleName“ gelöscht.', + 'role_delete_users_assigned' => 'Dieser Rolle sind :userCount Benutzer zugewiesen. Wenn Sie die Benutzer aus dieser Rolle migrieren möchten, wählen Sie unten eine neue Rolle aus.', 'role_delete_no_migration' => "Den Benutzern keine andere Rolle zuordnen", - 'role_delete_sure' => 'Sind Sie sicher, dass Sie diese Rolle löschen möchten?', + 'role_delete_sure' => 'Möchten Sie diese Rolle wirklich löschen?', 'role_edit' => 'Rolle bearbeiten', 'role_details' => 'Rollendetails', 'role_name' => 'Rollenname', 'role_desc' => 'Kurzbeschreibung der Rolle', - 'role_mfa_enforced' => 'Benötigt Mehrfach-Faktor-Authentifizierung', + 'role_mfa_enforced' => 'Erfordert eine Multi-Faktor-Authentifizierung', 'role_external_auth_id' => 'Externe Authentifizierungs-IDs', 'role_system' => 'System-Berechtigungen', 'role_manage_users' => 'Benutzer verwalten', - 'role_manage_roles' => 'Rollen und Rollen-Berechtigungen verwalten', - 'role_manage_entity_permissions' => 'Alle Buch-, Kapitel- und Seiten-Berechtigungen verwalten', - 'role_manage_own_entity_permissions' => 'Nur Berechtigungen eigener Bücher, Kapitel und Seiten verwalten', + 'role_manage_roles' => 'Rollen und Rollenberechtigungen verwalten', + 'role_manage_entity_permissions' => 'Alle Berechtigungen für Bücher, Kapitel und Seiten verwalten', + 'role_manage_own_entity_permissions' => 'Berechtigungen für das eigene Buch, Kapitel und Seiten verwalten', 'role_manage_page_templates' => 'Seitenvorlagen verwalten', 'role_access_api' => 'Systemzugriffs-API', 'role_manage_settings' => 'Globaleinstellungen verwalten', 'role_export_content' => 'Inhalt exportieren', 'role_import_content' => 'Inhalt importieren', 'role_editor_change' => 'Seiten-Editor ändern', - 'role_notifications' => 'Empfangen und Verwalten von Benachrichtigungen', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_notifications' => 'Benachrichtigungen empfangen und verwalten', + 'role_permission_note_users_and_roles' => 'Diese Berechtigungen ermöglichen technisch gesehen auch die Anzeige und Suche nach Benutzern und Rollen im System.', 'role_asset' => 'Berechtigungen', - 'roles_system_warning' => 'Beachten Sie, dass der Zugriff auf eine der oben genannten drei Berechtigungen einem Benutzer erlauben kann, seine eigenen Berechtigungen oder die Rechte anderer im System zu ändern. Weisen Sie nur Rollen, mit diesen Berechtigungen, vertrauenswürdigen Benutzern zu.', - 'role_asset_desc' => 'Diese Berechtigungen gelten für den Standard-Zugriff innerhalb des Systems. Berechtigungen für Bücher, Kapitel und Seiten überschreiben diese Berechtigungenen.', - 'role_asset_admins' => 'Administratoren erhalten automatisch Zugriff auf alle Inhalte, aber diese Optionen können Oberflächenoptionen ein- oder ausblenden.', - 'role_asset_image_view_note' => 'Das bezieht sich auf die Sichtbarkeit innerhalb des Bildmanagers. Der tatsächliche Zugriff auf hochgeladene Bilddateien hängt von der Speicheroption des Systems für Bilder ab.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'roles_system_warning' => 'Beachten Sie, dass der Zugriff auf eine der drei oben genannten Berechtigungen es einem Benutzer ermöglichen kann, seine eigenen Berechtigungen oder die Berechtigungen anderer Benutzer im System zu ändern. Weisen Sie Rollen mit diesen Berechtigungen nur vertrauenswürdigen Benutzern zu.', + 'role_asset_desc' => 'Diese Berechtigungen regeln den Standard mäßigen Zugriff auf die Assets im System. Berechtigungen für Bücher, Kapitel und Seiten haben Vorrang vor diesen Berechtigungen.', + 'role_asset_admins' => 'Administratoren erhalten automatisch Zugriff auf alle Inhalte, doch mit diesen Optionen können Elemente der Benutzeroberfläche ein- oder ausgeblendet werden.', + 'role_asset_image_view_note' => 'Dies betrifft die Sichtbarkeit innerhalb des Bildmanagers. Der tatsächliche Zugriff auf hochgeladene Bilddateien hängt von der gewählten Speicheroption des Systems ab.', + 'role_asset_users_note' => 'Diese Berechtigungen ermöglichen technisch gesehen auch die Anzeige und Suche nach Benutzern im System.', 'role_all' => 'Alle', 'role_own' => 'Eigene', - 'role_controlled_by_asset' => 'Berechtigungen werden vom Uploadziel bestimmt', + 'role_controlled_by_asset' => 'Abhängig von dem Asset, in das sie hochgeladen werden', 'role_save' => 'Rolle speichern', 'role_users' => 'Dieser Rolle zugeordnete Benutzer', - 'role_users_none' => 'Bisher sind dieser Rolle keine Benutzer zugeordnet', + 'role_users_none' => 'Derzeit sind diesem Rollentyp keine Benutzer zugewiesen', // Users 'users' => 'Benutzer', - 'users_index_desc' => 'Erstellen und Verwalten Sie individuelle Benutzerkonten innerhalb des Systems. Benutzerkonten werden zur Anmeldung und Besitz von Inhalten und Aktivitäten verwendet. Zugriffsberechtigungen sind in erster Linie rollenbasiert, aber Besitz von Benutzerinhalten kann unter anderem auch Berechtigungen beeinflussen.', + 'users_index_desc' => 'Erstellen und verwalten Sie individuelle Benutzerkonten innerhalb des Systems. Benutzerkonten dienen der Anmeldung sowie der Zuordnung von Inhalten und Aktivitäten. Zugriffsberechtigungen sind in erster Linie rollen basiert, doch unter anderem kann auch die Eigentümerschaft an Inhalten durch den Benutzer die Berechtigungen und den Zugriff beeinflussen.', 'user_profile' => 'Benutzerprofil', 'users_add_new' => 'Benutzer hinzufügen', 'users_search' => 'Benutzer suchen', 'users_latest_activity' => 'Neueste Aktivitäten', 'users_details' => 'Benutzerdetails', 'users_details_desc' => 'Legen Sie für diesen Benutzer einen Anzeigenamen und eine E-Mail-Adresse fest. Die E-Mail-Adresse wird bei der Anmeldung verwendet.', - 'users_details_desc_no_email' => 'Legen Sie für diesen Benutzer einen Anzeigenamen fest, damit andere ihn erkennen können.', + 'users_details_desc_no_email' => 'Legen Sie einen Anzeigenamen für diesen Benutzer fest, damit andere ihn erkennen können.', 'users_role' => 'Benutzerrollen', - 'users_role_desc' => 'Wählen Sie aus, welchen Rollen dieser Benutzer zugeordnet werden soll. Wenn ein Benutzer mehreren Rollen zugeordnet ist, werden die Berechtigungen dieser Rollen gestapelt und er erhält alle Fähigkeiten der zugewiesenen Rollen.', + 'users_role_desc' => 'Wählen Sie aus, welchen Rollen dieser Benutzer zugewiesen werden soll. Wenn einem Benutzer mehrere Rollen zugewiesen sind, stapeln sich die Berechtigungen dieser Rollen, und er erhält alle Funktionen der zugewiesenen Rollen.', 'users_password' => 'Benutzerpasswort', - 'users_password_desc' => 'Legen Sie ein Passwort fest, mit dem Sie sich anmelden möchten. Diese muss mindestens 8 Zeichen lang sein.', - 'users_send_invite_text' => 'Sie können diesem Benutzer eine Einladungs-E-Mail senden, die es ihm erlaubt, sein eigenes Passwort zu setzen, andernfalls können Sie sein Passwort selbst setzen.', - 'users_send_invite_option' => 'Benutzer-Einladungs-E-Mail senden', + 'users_password_desc' => 'Legen Sie ein Passwort fest, mit dem Sie sich bei der Anwendung anmelden. Es muss mindestens 8 Zeichen lang sein.', + 'users_send_invite_text' => 'Sie können diesem Benutzer eine Einladungs-E-Mail senden, mit der er sein eigenes Passwort festlegen kann, oder Sie können das Passwort selbst festlegen.', + 'users_send_invite_option' => 'Es erfolgt die Zusendung einer E-Mail mit einer Einladung an den Nutzer', 'users_external_auth_id' => 'Externe Authentifizierungs-ID', - 'users_external_auth_id_desc' => 'Wenn ein externes Authentifizierungssystem verwendet wird (z. B. SAML2, OIDC oder LDAP) ist dies die ID, die diesen BookStack-Benutzer mit dem Authentifizierungs-Systemkonto verknüpft. Sie können dieses Feld ignorieren, wenn Sie die Standard-E-Mail-basierte Authentifizierung verwenden.', + 'users_external_auth_id_desc' => 'Wenn ein externes Authentifizierungssystem verwendet wird (z. B. SAML2, OIDC oder LDAP), ist dies die ID, die diesen BookStack Benutzer mit dem Konto im Authentifizierungssystem verknüpft. Bei Verwendung der Standard mäßigen E-Mail-basierten Authentifizierung können Sie dieses Feld ignorieren.', 'users_password_warning' => 'Füllen Sie die untenstehenden Felder nur aus, wenn Sie das Passwort für diesen Benutzer ändern möchten.', - 'users_system_public' => 'Dieser Benutzer repräsentiert alle unangemeldeten Benutzer, die diese Seite betrachten. Er kann nicht zum Anmelden benutzt werden, sondern wird automatisch zugeordnet.', + 'users_system_public' => 'Dieser Benutzer steht für alle Gastbenutzer, die Ihre Instanz besuchen. Er kann nicht zum Einloggen verwendet werden, wird jedoch automatisch zugewiesen.', 'users_delete' => 'Benutzer löschen', 'users_delete_named' => 'Benutzer ":userName" löschen', - 'users_delete_warning' => 'Der Benutzer ":userName" wird aus dem System gelöscht.', - 'users_delete_confirm' => 'Sind Sie sicher, dass Sie diesen Benutzer löschen möchten?', - 'users_migrate_ownership' => 'Besitz migrieren', - 'users_migrate_ownership_desc' => 'Wählen Sie hier einen Benutzer, wenn Sie möchten, dass ein anderer Benutzer der Besitzer aller Einträge wird, die diesem Benutzer derzeit gehören.', + 'users_delete_warning' => 'Dadurch wird der Benutzer mit dem Namen „:userName“ vollständig aus dem System gelöscht.', + 'users_delete_confirm' => 'Möchten Sie diesen Benutzer wirklich löschen?', + 'users_migrate_ownership' => 'Eigentumsverhältnisse übertragen', + 'users_migrate_ownership_desc' => 'Wählen Sie hier einen Benutzer aus, wenn Sie möchten, dass ein anderer Benutzer Eigentümer aller Elemente wird, die derzeit diesem Benutzer gehören.', 'users_none_selected' => 'Kein Benutzer ausgewählt', 'users_edit' => 'Benutzer bearbeiten', 'users_edit_profile' => 'Profil bearbeiten', - 'users_avatar' => 'Benutzer-Bild', - 'users_avatar_desc' => 'Das Bild sollte eine Auflösung von 256x256px haben.', + 'users_avatar' => 'Benutzer-Avatar', + 'users_avatar_desc' => 'Wähle ein Bild aus, das diesen Benutzer repräsentieren soll. Es sollte etwa 256px im Quadrat groß sein.', 'users_preferred_language' => 'Bevorzugte Sprache', - 'users_preferred_language_desc' => 'Diese Option ändert die Sprache, die für die Benutzeroberfläche der Anwendung verwendet wird. Dies hat keinen Einfluss auf von Benutzern erstellte Inhalte.', - 'users_social_accounts' => 'Social-Media Konten', - 'users_social_accounts_desc' => 'Zeigt den Status der verbundenen sozialen Konten für diesen Benutzer an. Social Accounts können zusätzlich zum primären Authentifizierungssystem für den Systemzugriff verwendet werden.', - 'users_social_accounts_info' => 'Hier können Sie andere Social-Media-Konten für eine schnellere und einfachere Anmeldung verknüpfen. Wenn Sie ein Social-Media Konto lösen, bleibt der Zugriff erhalten. Entfernen Sie in diesem Falle die Berechtigung in Ihren Profil-Einstellungen des verknüpften Social-Media-Kontos.', - 'users_social_connect' => 'Social-Media-Konto verknüpfen', - 'users_social_disconnect' => 'Social-Media-Konto löschen', + 'users_preferred_language_desc' => 'Mit dieser Option können Sie die Sprache der Benutzeroberfläche der Anwendung ändern. Dies hat keine Auswirkungen auf von Benutzern erstellte Inhalte.', + 'users_social_accounts' => 'Social-Media-Konten', + 'users_social_accounts_desc' => 'Den Status der mit diesem Benutzer verknüpften Social-Media-Konten anzeigen. Social-Media-Konten können zusätzlich zum primären Authentifizierungssystem für den Systemzugang verwendet werden.', + 'users_social_accounts_info' => 'Hier kannst du deine anderen Konten verknüpfen, um dich schneller und einfacher anzumelden. Wenn du ein Konto hier entkoppelst, wird der zuvor erteilte Zugriff dadurch nicht widerrufen. Den Zugriff kannst du in den Profileinstellungen des verknüpften sozialen Kontos widerrufen.', + 'users_social_connect' => 'Konto verbinden', + 'users_social_disconnect' => 'Konto trennen', 'users_social_status_connected' => 'Verbunden', - 'users_social_status_disconnected' => 'Getrennt', - 'users_social_connected' => ':socialAccount-Konto wurde erfolgreich mit dem Profil verknüpft.', - 'users_social_disconnected' => ':socialAccount-Konto wurde erfolgreich vom Profil gelöst.', + 'users_social_status_disconnected' => 'Nicht verbunden', + 'users_social_connected' => ':socialAccount wurde erfolgreich mit Ihrem Profil verknüpft.', + 'users_social_disconnected' => ':socialAccount wurde erfolgreich von Ihrem Profil getrennt.', 'users_api_tokens' => 'API-Token', - 'users_api_tokens_desc' => 'Erstellen und verwalten Sie die Zugangs-Tokens zur Authentifizierung mit der BookStack REST API. Berechtigungen für die API werden über den Benutzer verwaltet, dem das Token gehört.', + 'users_api_tokens_desc' => 'Erstellen und verwalten Sie die Zugriffstoken, die zur Authentifizierung bei der BookStack REST API verwendet werden. Die Berechtigungen für die API werden über den Benutzer verwaltet, dem das Token zugeordnet ist.', 'users_api_tokens_none' => 'Für diesen Benutzer wurden keine API-Token erstellt', 'users_api_tokens_create' => 'Token erstellen', - 'users_api_tokens_expires' => 'Endet', + 'users_api_tokens_expires' => 'Gültig bis', 'users_api_tokens_docs' => 'API Dokumentation', 'users_mfa' => 'Multi-Faktor-Authentifizierung', - 'users_mfa_desc' => 'Richten Sie Multi-Faktor-Authentifizierung als zusätzliche Sicherheitsstufe für Ihr Benutzerkonto ein.', + 'users_mfa_desc' => 'Richten Sie die Multi-Faktor-Authentifizierung als zusätzliche Sicherheitsstufe für Ihr Benutzerkonto ein.', 'users_mfa_x_methods' => ':count Methode konfiguriert|:count Methoden konfiguriert', 'users_mfa_configure' => 'Methoden konfigurieren', // API Tokens 'user_api_token_create' => 'Neuen API-Token erstellen', 'user_api_token_name' => 'Name', - 'user_api_token_name_desc' => 'Geben Sie Ihrem Token einen aussagekräftigen Namen als spätere Erinnerung an seinen Verwendungszweck.', + 'user_api_token_name_desc' => 'Gib deinem Token einen aussagekräftigen Namen, damit du später noch weißt, wofür er gedacht ist.', 'user_api_token_expiry' => 'Ablaufdatum', - 'user_api_token_expiry_desc' => 'Legen Sie ein Datum fest, an dem dieser Token abläuft. Nach diesem Datum funktionieren Anfragen, die mit diesem Token gestellt werden, nicht mehr. Wenn Sie dieses Feld leer lassen, wird ein Ablaufdatum von 100 Jahren in der Zukunft festgelegt.', - 'user_api_token_create_secret_message' => 'Unmittelbar nach der Erstellung dieses Tokens wird eine "Token ID" & ein "Token Kennwort" generiert und angezeigt. Das Kennwort wird nur ein einziges Mal angezeigt. Stellen Sie also sicher, dass Sie den Inhalt an einen sicheren Ort kopieren, bevor Sie fortfahren.', + 'user_api_token_expiry_desc' => 'Legen Sie ein Ablaufdatum für dieses Token fest. Nach diesem Datum funktionieren Anfragen, die mit diesem Token gestellt werden, nicht mehr. Wenn Sie dieses Feld leer lassen, wird das Ablaufdatum auf 100 Jahre in der Zukunft gesetzt.', + 'user_api_token_create_secret_message' => 'Unmittelbar nach der Erstellung dieses Tokens werden eine „Token-ID“ und ein „Token-Geheimnis“ generiert und angezeigt. Das Geheimnis wird nur einmal angezeigt. Kopieren Sie den Wert daher unbedingt an einen sicheren Ort, bevor Sie fortfahren.', 'user_api_token' => 'API-Token', 'user_api_token_id' => 'Token ID', - 'user_api_token_id_desc' => 'Dies ist ein nicht editierbarer, vom System generierter Identifikator für diesen Token, welcher bei API-Anfragen angegeben werden muss.', - 'user_api_token_secret' => 'Token Kennwort', - 'user_api_token_secret_desc' => 'Dies ist ein systemgeneriertes Kennwort für diesen Token, das bei API-Anfragen zur Verfügung gestellt werden muss. Es wird nur dieses eine Mal angezeigt, deshalb kopieren Sie diesen Wert an einen sicheren und geschützten Ort.', - 'user_api_token_created' => 'Token erstellt :timeAgo', + 'user_api_token_id_desc' => 'Dies ist eine nicht bearbeitbare, vom System generierte Kennung für dieses Token, die in API-Anfragen angegeben werden muss.', + 'user_api_token_secret' => 'Token Geheimnis', + 'user_api_token_secret_desc' => 'Dies ist ein vom System generiertes Geheimnis für dieses Token, das in API-Anfragen angegeben werden muss. Es wird nur dieses eine Mal angezeigt; speichern Sie diesen Wert daher an einem sicheren Ort.', + 'user_api_token_created' => 'Token erstellt vor :timeAgo', 'user_api_token_updated' => 'Token aktualisiert :timeAgo', 'user_api_token_delete' => 'Lösche Token', - 'user_api_token_delete_warning' => 'Dies löscht den API-Token mit dem Namen \':tokenName\' vollständig aus dem System.', - 'user_api_token_delete_confirm' => 'Sind Sie sicher, dass Sie diesen API-Token löschen möchten?', + 'user_api_token_delete_warning' => 'Dadurch wird dieser API-Token mit dem Namen „:tokenName“ vollständig aus dem System gelöscht.', + 'user_api_token_delete_confirm' => 'Möchten Sie diesen API-Token wirklich löschen?', // Webhooks 'webhooks' => 'Webhooks', - 'webhooks_index_desc' => 'Webhooks sind eine Möglichkeit, Daten an externe URLs zu senden, wenn bestimmte Aktionen und Ereignisse im System auftreten, was eine ereignisbasierte Integration mit externen Plattformen wie Messaging- oder Benachrichtigungssystemen ermöglicht.', - 'webhooks_x_trigger_events' => ':count Auslöserereignis|:count Auslöserereignisse', + 'webhooks_index_desc' => 'Webhooks sind eine Möglichkeit, Daten an externe URLs zu senden, wenn bestimmte Aktionen und Ereignisse innerhalb des Systems auftreten. Dies ermöglicht eine Ereignis-basierte Integration mit externen Plattformen wie Messaging oder Benachrichtigungssystemen.', + 'webhooks_x_trigger_events' => ':count ausgelöstes Ereignis|:count ausgelöste Ereignisse', 'webhooks_create' => 'Neuen Webhook erstellen', 'webhooks_none_created' => 'Es wurden noch keine Webhooks erstellt.', 'webhooks_edit' => 'Webhook bearbeiten', 'webhooks_save' => 'Webhook speichern', 'webhooks_details' => 'Webhook-Details', - 'webhooks_details_desc' => 'Geben Sie einen benutzerfreundlichen Namen und einen POST-Endpunkt als Ort an, an den die Webhook-Daten gesendet werden sollen.', + 'webhooks_details_desc' => 'Geben Sie einen benutzerfreundlichen Namen und einen POST-Endpunkt als Ziel für die zu sendenden Webhook-Daten an.', 'webhooks_events' => 'Webhook Ereignisse', - 'webhooks_events_desc' => 'Wählen Sie alle Ereignisse, die diesen Webhook auslösen sollen.', - 'webhooks_events_warning' => 'Beachten Sie, dass diese Ereignisse für alle ausgewählten Ereignisse ausgelöst werden, auch wenn benutzerdefinierte Berechtigungen angewendet werden. Stellen Sie sicher, dass die Verwendung dieses Webhook keine vertraulichen Inhalte enthüllt.', + 'webhooks_events_desc' => 'Wählen Sie alle Ereignisse aus, die den Aufruf dieses Webhooks auslösen sollen.', + 'webhooks_events_warning' => 'Beachten Sie, dass diese Ereignisse für alle ausgewählten Ereignisse ausgelöst werden, auch wenn benutzerdefinierte Berechtigungen gelten. Stellen Sie sicher, dass durch die Verwendung dieses Webhooks keine vertraulichen Inhalte offengelegt werden.', 'webhooks_events_all' => 'Alle System-Ereignisse', 'webhooks_name' => 'Webhook-Name', 'webhooks_timeout' => 'Webhook Request Timeout (Sekunden)', @@ -303,10 +302,10 @@ 'webhooks_active' => 'Webhook aktiv', 'webhook_events_table_header' => 'Ereignisse', 'webhooks_delete' => 'Webhook löschen', - 'webhooks_delete_warning' => 'Dies wird diesen Webhook mit dem Namen \':webhookName\' vollständig aus dem System löschen.', - 'webhooks_delete_confirm' => 'Sind Sie sicher, dass Sie diesen Webhook löschen möchten?', - 'webhooks_format_example' => 'Webhook Format Beispiel', - 'webhooks_format_example_desc' => 'Webhook Daten werden als POST-Anfrage an den konfigurierten Endpunkt als JSON im folgenden Format gesendet. Die Eigenschaften "related_item" und "url" sind optional und hängen vom Typ des ausgelösten Ereignisses ab.', + 'webhooks_delete_warning' => 'Dadurch wird dieser Webhook mit dem Namen „:webhookName“ vollständig aus dem System gelöscht.', + 'webhooks_delete_confirm' => 'Möchtest du diesen Webhook wirklich löschen?', + 'webhooks_format_example' => 'Beispiel für ein Webhook-Format', + 'webhooks_format_example_desc' => 'Webhook-Daten werden als POST-Anfrage im JSON-Format an den konfigurierten Endpunkt gesendet und entsprechen dabei dem unten angegebenen Format. Die Eigenschaften „related_item“ und „url“ sind optional und hängen von der Art des ausgelösten Ereignisses ab.', 'webhooks_status' => 'Webhook-Status', 'webhooks_last_called' => 'Zuletzt aufgerufen:', 'webhooks_last_errored' => 'Letzter Fehler:', @@ -314,11 +313,11 @@ // Licensing 'licenses' => 'Lizenzen', - 'licenses_desc' => 'Diese Seite beschreibt Lizenzinformationen für BookStack zusätzlich zu den Projekten und Bibliotheken, die in BookStack verwendet werden. Viele aufgelistete Projekte dürfen nur in einem Entwicklungskontext verwendet werden.', - 'licenses_bookstack' => 'BookStack-Lizenz', - 'licenses_php' => 'PHP-Bibliothekslizenzen', - 'licenses_js' => 'JavaScript-Bibliothekslizenzen', - 'licenses_other' => 'Andere Lizenzen', + 'licenses_desc' => 'Auf dieser Seite finden Sie Lizenzinformationen zu BookStack sowie zu den Projekten und Bibliotheken, die in BookStack verwendet werden. Viele der aufgeführten Projekte dürfen möglicherweise nur im Entwicklungskontext genutzt werden.', + 'licenses_bookstack' => 'BookStack Lizenz', + 'licenses_php' => 'Lizenzen für PHP-Bibliotheken', + 'licenses_js' => 'Lizenzen für JavaScript-Bibliotheken', + 'licenses_other' => 'Sonstige Lizenzen', 'license_details' => 'Lizenzdetails', //! If editing translations files directly please ignore this in all diff --git a/lang/de_informal/entities.php b/lang/de_informal/entities.php index a3e6e5f369c..16502051cee 100644 --- a/lang/de_informal/entities.php +++ b/lang/de_informal/entities.php @@ -63,10 +63,10 @@ 'import_delete_desc' => 'Dies löscht die hochgeladene ZIP-Datei und kann nicht rückgängig gemacht werden.', 'import_errors' => 'Importfehler', 'import_errors_desc' => 'Die folgenden Fehler sind während des Importversuchs aufgetreten:', - 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', - 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter', - 'breadcrumb_siblings_for_book' => 'Navigiere in Büchern', - 'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf', + 'breadcrumb_siblings_for_page' => 'Durch die untergeordneten Elemente der Seite navigieren', + 'breadcrumb_siblings_for_chapter' => 'Durch die Unterelemente des Kapitels navigieren', + 'breadcrumb_siblings_for_book' => 'Durch die Unterordner des Buches navigieren', + 'breadcrumb_siblings_for_bookshelf' => 'Durch die untergeordneten Elemente des Regals navigieren', // Permissions and restrictions 'permissions' => 'Berechtigungen', diff --git a/lang/de_informal/settings.php b/lang/de_informal/settings.php index fa175187b80..97ad607d6bc 100644 --- a/lang/de_informal/settings.php +++ b/lang/de_informal/settings.php @@ -76,8 +76,8 @@ 'reg_confirm_restrict_domain_placeholder' => 'Keine Einschränkung gesetzt', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', + 'sorting' => 'Listen & Sortieren', + 'sorting_book_default' => 'Standardregel für die Sortierung von Büchern', 'sorting_book_default_desc' => 'Wähle die Standard-Sortierregel aus, die auf neue Bücher angewendet werden soll. Dies wirkt sich nicht auf bestehende Bücher aus und kann pro Buch überschrieben werden.', 'sorting_rules' => 'Sortierregeln', 'sorting_rules_desc' => 'Dies sind vordefinierte Sortieraktionen, die auf Inhalte im System angewendet werden können.', @@ -104,8 +104,8 @@ 'sort_rule_op_updated_date' => 'Aktualisierungsdatum', 'sort_rule_op_chapters_first' => 'Kapitel zuerst', 'sort_rule_op_chapters_last' => 'Kapitel zuletzt', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sorting_page_limits' => 'Anzeigebegrenzungen pro Seite', + 'sorting_page_limits_desc' => 'Legen Sie fest, wie viele Elemente pro Seite in den verschiedenen Listen des Systems angezeigt werden sollen. In der Regel ist eine geringere Anzahl leistungsfähiger, während eine höhere Anzahl das Blättern durch mehrere Seiten überflüssig macht. Es wird empfohlen, ein Vielfaches von 6 zu verwenden.', // Maintenance settings 'maint' => 'Wartung', @@ -198,13 +198,13 @@ 'role_import_content' => 'Inhalt importieren', 'role_editor_change' => 'Seiteneditor ändern', 'role_notifications' => 'Empfangen und Verwalten von Benachrichtigungen', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => 'Diese Berechtigungen ermöglichen technisch gesehen auch die Anzeige und Suche nach Benutzern und Rollen im System.', 'role_asset' => 'Berechtigungen', 'roles_system_warning' => 'Beachte, dass der Zugriff auf eine der oben genannten drei Berechtigungen einem Benutzer erlauben kann, seine eigenen Berechtigungen oder die Rechte anderer im System zu ändern. Weise nur Rollen mit diesen Berechtigungen vertrauenswürdigen Benutzern zu.', 'role_asset_desc' => 'Diese Berechtigungen gelten für den Standard-Zugriff innerhalb des Systems. Berechtigungen für Bücher, Kapitel und Seiten überschreiben diese Berechtigungen.', 'role_asset_admins' => 'Administratoren erhalten automatisch Zugriff auf alle Inhalte, aber diese Optionen können Oberflächenoptionen ein- oder ausblenden.', 'role_asset_image_view_note' => 'Das bezieht sich auf die Sichtbarkeit innerhalb des Bildmanagers. Der tatsächliche Zugriff auf hochgeladene Bilddateien hängt von der Speicheroption des Systems für Bilder ab.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Diese Berechtigungen ermöglichen technisch gesehen auch die Anzeige und Suche nach Benutzern im System.', 'role_all' => 'Alle', 'role_own' => 'Eigene', 'role_controlled_by_asset' => 'Berechtigungen werden vom Uploadziel bestimmt', diff --git a/lang/ko/editor.php b/lang/ko/editor.php index b5c4ae8070c..a274586c180 100644 --- a/lang/ko/editor.php +++ b/lang/ko/editor.php @@ -178,5 +178,5 @@ 'shortcuts_intro' => '편집기에서 사용할 수 있는 바로 가기는 다음과 같습니다:', 'windows_linux' => '(윈도우/리눅스)', 'mac' => '(맥)', - 'description' => '상세정보', + 'description' => '설명', ]; diff --git a/lang/ko/errors.php b/lang/ko/errors.php index 9fa56ffcdc3..ca7c54573a2 100644 --- a/lang/ko/errors.php +++ b/lang/ko/errors.php @@ -71,8 +71,8 @@ 'book_not_found' => '책이 없습니다.', 'page_not_found' => '문서가 없습니다.', 'chapter_not_found' => '챕터가 없습니다.', - 'selected_book_not_found' => '고른 책이 없습니다.', - 'selected_book_chapter_not_found' => '고른 책이나 챕터가 없습니다.', + 'selected_book_not_found' => '선택된 책이 없습니다.', + 'selected_book_chapter_not_found' => '선택된 책이나 챕터가 없습니다', 'guests_cannot_save_drafts' => 'Guest는 초안 문서를 보관할 수 없습니다.', // Users diff --git a/lang/pt/common.php b/lang/pt/common.php index a0d83ac1e73..3ffc6680b3c 100644 --- a/lang/pt/common.php +++ b/lang/pt/common.php @@ -30,8 +30,8 @@ 'create' => 'Criar', 'update' => 'Atualizar', 'edit' => 'Editar', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'Arquivar', + 'unarchive' => 'Desarquivar', 'sort' => 'Ordenar', 'move' => 'Mover', 'copy' => 'Copiar', diff --git a/lang/pt/editor.php b/lang/pt/editor.php index b60f813b51b..e3069909c67 100644 --- a/lang/pt/editor.php +++ b/lang/pt/editor.php @@ -13,7 +13,7 @@ 'cancel' => 'Cancelar', 'save' => 'Guardar', 'close' => 'Fechar', - 'apply' => 'Apply', + 'apply' => 'Aplicar', 'undo' => 'Anular', 'redo' => 'Refazer', 'left' => 'Esquerda', @@ -166,7 +166,7 @@ 'about' => 'Sobre o editor', 'about_title' => 'Sobre o Editor WYSIWYG', 'editor_license' => 'Editor da licença de direitos autorais', - 'editor_lexical_license' => 'This editor is built as a fork of :lexicalLink which is distributed under the MIT license.', + 'editor_lexical_license' => 'Este editor é criado como um fork do :lexicaLink que é distribuído sob a licença MIT.', 'editor_lexical_license_link' => 'Full license details can be found here.', 'editor_tiny_license' => 'Este editor foi criado com :tinyLink que é fornecido sob a licença MIT.', 'editor_tiny_license_link' => 'Os dados relativos aos direitos de autor e à licença do TinyMCE podem ser encontrados aqui.', diff --git a/lang/pt/entities.php b/lang/pt/entities.php index 91fe6d90cd1..e882c79d001 100644 --- a/lang/pt/entities.php +++ b/lang/pt/entities.php @@ -23,7 +23,7 @@ 'meta_updated' => 'Atualizado :timeLength', 'meta_updated_name' => 'Atualizado :timeLength por :user', 'meta_owned_name' => 'Propriedade de :user', - 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', + 'meta_reference_count' => 'Referenciado por :count item├Referenciado por :count itens', 'entity_select' => 'Seleção de Entidade', 'entity_select_lack_permission' => 'Não tem as permissões necessárias para selecionar este item', 'images' => 'Imagens', @@ -39,13 +39,13 @@ 'export_pdf' => 'Arquivo PDF', 'export_text' => 'Arquivo Texto', 'export_md' => 'Ficheiro Markdown', - 'export_zip' => 'Portable ZIP', - 'default_template' => 'Default Page Template', - 'default_template_explain' => 'Assign a page template that will be used as the default content for all pages created within this item. Keep in mind this will only be used if the page creator has view access to the chosen template page.', - 'default_template_select' => 'Select a template page', - 'import' => 'Import', - 'import_validate' => 'Validate Import', - 'import_desc' => 'Import books, chapters & pages using a portable zip export from the same, or a different, instance. Select a ZIP file to proceed. After the file has been uploaded and validated you\'ll be able to configure & confirm the import in the next view.', + 'export_zip' => 'ZIP Portátil', + 'default_template' => 'Modelo de página padrão', + 'default_template_explain' => 'Atribuir um modelo de página que será usado como o conteúdo padrão para todas as páginas criadas dentro deste item. Tenha em mente que isto só será usado se o criador da página tiver acesso à página de modelo escolhida.', + 'default_template_select' => 'Selecione uma página de modelo', + 'import' => 'Importar', + 'import_validate' => 'Validar Importação', + 'import_desc' => 'Importar livros, capítulos e páginas usando uma exportação ZIP portátil da mesma ou uma instância diferente. Selecione um arquivo ZIP para prosseguir. Após o carregamento e validação do arquivo, conseguirá configurar e confirmar a importação na próxima visualização.', 'import_zip_select' => 'Select ZIP file to upload', 'import_zip_validation_errors' => 'Errors were detected while validating the provided ZIP file:', 'import_pending' => 'Pending Imports', @@ -60,7 +60,7 @@ 'import_location' => 'Import Location', 'import_location_desc' => 'Select a target location for your imported content. You\'ll need the relevant permissions to create within the location you choose.', 'import_delete_confirm' => 'Are you sure you want to delete this import?', - 'import_delete_desc' => 'This will delete the uploaded import ZIP file, and cannot be undone.', + 'import_delete_desc' => 'Isto irá eliminar o arquivo ZIP de importação enviado e não pode ser desfeito.', 'import_errors' => 'Import Errors', 'import_errors_desc' => 'The follow errors occurred during the import attempt:', 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', @@ -170,8 +170,8 @@ 'books_search_this' => 'Pesquisar neste livro', 'books_navigation' => 'Navegação do Livro', 'books_sort' => 'Ordenar Conteúdos do Livro', - '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_desc' => 'Mova capítulos e páginas de um livro para reorganizar o seu conteúdo. É possível acrescentar outros livros, o que permite uma movimentação fácil de capítulos e páginas entre livros. Opcionalmente, uma regra de organização automática pode ser definida para classificar automaticamente o conteúdo deste livro após alterações.', + 'books_sort_auto_sort' => '', 'books_sort_auto_sort_active' => 'Auto Sort Active: :sortName', 'books_sort_named' => 'Ordenar Livro :bookName', 'books_sort_name' => 'Ordenar por Nome', From e033578feaa86dd0a3ac8dc4137bfec66a1baee7 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 5 Apr 2026 22:43:15 +0100 Subject: [PATCH 099/204] Updated translator & dependency attribution before release v26.03.3 --- .github/translators.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/translators.txt b/.github/translators.txt index 97ab6c3fc6f..037887bcc36 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -444,7 +444,7 @@ Irjan Olsen (Irch) :: Norwegian Bokmal Aleksandar Jovanovic (jovanoviczaleksandar) :: Serbian (Cyrillic) Red (RedVortex) :: Hebrew xgrug :: Chinese Simplified -HrCalmar :: Danish +Calle Calmar (HrCalmar) :: Danish Avishay Rapp (AvishayRapp) :: Hebrew matthias4217 :: French Berke BOYLU2 (berkeboylu2) :: Turkish @@ -534,3 +534,6 @@ Charllys Fernandes (CharllysFernandes) :: Portuguese, Brazilian Ilgiz Zigangirov (inov8) :: Russian Max Israelsson (Blezie) :: Swedish Skiddybison5924 (chris-devel0per) :: German +Veyilla Nightwhisper (Veyilla) :: German +João Barbosa (hypeedd) :: Portuguese +Abcdefg Hijklmn (collatek) :: Korean From c33853ed84bcefdd41fd12013451f14a69efc0d3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 8 Apr 2026 21:02:20 +0100 Subject: [PATCH 100/204] Maintenance: Updated NPM packages (#6090) * Maintenance: Updated NPM packages Includes typescript update to 6. Needed to update some typescript config to align with actual module environment used and built by esbuild. * Maintenance: Fixed testing issues after NPM dep version changes * Maintenance: Updated JS test workflow step version * Maintenance: Updated approach used for TS config in jest config --- .github/workflows/test-js.yml | 2 +- jest.config.ts | 14 +- package-lock.json | 1279 ++++++++++++++++++--------------- package.json | 24 +- tsconfig.json | 15 +- 5 files changed, 720 insertions(+), 614 deletions(-) diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml index 13f9a8a9819..379f1ebfaa7 100644 --- a/.github/workflows/test-js.yml +++ b/.github/workflows/test-js.yml @@ -17,7 +17,7 @@ jobs: if: ${{ github.ref != 'refs/heads/l10n_development' }} runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install NPM deps run: npm ci diff --git a/jest.config.ts b/jest.config.ts index 53bfceb053e..f7b5596de46 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -5,7 +5,15 @@ import type {Config} from 'jest'; import {pathsToModuleNameMapper} from "ts-jest"; -import { compilerOptions } from './tsconfig.json'; +import fs from "node:fs"; + +const { compilerOptions } = JSON.parse(fs.readFileSync('./tsconfig.json', 'utf8')); +const compilerPaths = compilerOptions.paths as Record; +const cleanedPaths: Record = {}; +Object.keys(compilerPaths).forEach((key) => { + const paths = compilerPaths[key]; + cleanedPaths[key] = paths.map(p => p.replace('./', '')); +}); const config: Config = { // All imported modules in your tests should be mocked automatically @@ -98,7 +106,7 @@ const config: Config = { // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module moduleNameMapper: { 'lexical/shared/invariant': 'resources/js/wysiwyg/lexical/core/shared/__mocks__/invariant', - ...pathsToModuleNameMapper(compilerOptions.paths), + ...pathsToModuleNameMapper(cleanedPaths), }, // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader @@ -111,7 +119,7 @@ const config: Config = { // notifyMode: "failure-change", // A preset that is used as a base for Jest's configuration - // preset: undefined, + preset: 'ts-jest', // Run tests from one or more projects // projects: undefined, diff --git a/package-lock.json b/package-lock.json index b6508f1e9e4..d239426ec2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "dependencies": { - "@codemirror/commands": "^6.10.2", + "@codemirror/commands": "^6.10.3", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.11", "@codemirror/lang-javascript": "^6.2.5", @@ -13,15 +13,14 @@ "@codemirror/lang-markdown": "^6.5.0", "@codemirror/lang-php": "^6.0.2", "@codemirror/lang-xml": "^6.1.0", - "@codemirror/language": "^6.12.2", + "@codemirror/language": "^6.12.3", "@codemirror/legacy-modes": "^6.5.2", - "@codemirror/state": "^6.5.4", + "@codemirror/state": "^6.6.0", "@codemirror/theme-one-dark": "^6.1.3", - "@codemirror/view": "^6.39.16", + "@codemirror/view": "^6.41.0", "@lezer/highlight": "^1.2.3", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", - "@types/jest": "^30.0.0", "codemirror": "^6.0.2", "idb-keyval": "^6.2.2", "markdown-it": "^14.1.1", @@ -32,19 +31,20 @@ "devDependencies": { "@eslint/js": "^10.0.1", "@lezer/generator": "^1.8.0", + "@types/jest": "^30.0.0", "@types/markdown-it": "^14.1.2", "@types/sortablejs": "^1.15.9", "chokidar-cli": "^3.0", - "esbuild": "^0.27.3", - "eslint": "^10.0.2", + "esbuild": "^0.28.0", + "eslint": "^10.2.0", "globals": "^17.4.0", - "jest": "^30.2.0", - "jest-environment-jsdom": "^30.2.0", + "jest": "^30.3.0", + "jest-environment-jsdom": "^30.3.0", "npm-run-all": "^4.1.5", - "sass": "^1.97.3", - "ts-jest": "^29.4.6", + "sass": "^1.99.0", + "ts-jest": "^29.4.9", "ts-node": "^10.9.2", - "typescript": "5.9.*" + "typescript": "6.0.*" } }, "node_modules/@asamuzakjp/css-color": { @@ -72,6 +72,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -223,6 +224,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -239,23 +241,23 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { @@ -575,13 +577,13 @@ } }, "node_modules/@codemirror/commands": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.2.tgz", - "integrity": "sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==", + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz", + "integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==", "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.4.0", + "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } @@ -684,9 +686,9 @@ } }, "node_modules/@codemirror/language": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz", - "integrity": "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", + "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", @@ -729,9 +731,9 @@ } }, "node_modules/@codemirror/state": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz", - "integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", + "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", "license": "MIT", "dependencies": { "@marijn/find-cluster-break": "^1.0.0" @@ -750,12 +752,12 @@ } }, "node_modules/@codemirror/view": { - "version": "6.39.16", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.16.tgz", - "integrity": "sha512-m6S22fFpKtOWhq8HuhzsI1WzUP/hB9THbDj0Tl5KX4gbO6Y91hwBl7Yky33NdvB6IffuRFiBxf1R8kJMyXmA4Q==", + "version": "6.41.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.41.0.tgz", + "integrity": "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA==", "license": "MIT", "dependencies": { - "@codemirror/state": "^6.5.0", + "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" @@ -901,21 +903,21 @@ } }, "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.1.0", + "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", "dev": true, "license": "MIT", "optional": true, @@ -924,9 +926,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, @@ -935,9 +937,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", "cpu": [ "ppc64" ], @@ -952,9 +954,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", "cpu": [ "arm" ], @@ -969,9 +971,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", "cpu": [ "arm64" ], @@ -986,9 +988,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", "cpu": [ "x64" ], @@ -1003,9 +1005,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], @@ -1020,9 +1022,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", "cpu": [ "x64" ], @@ -1037,9 +1039,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", "cpu": [ "arm64" ], @@ -1054,9 +1056,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", "cpu": [ "x64" ], @@ -1071,9 +1073,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", "cpu": [ "arm" ], @@ -1088,9 +1090,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", "cpu": [ "arm64" ], @@ -1105,9 +1107,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", "cpu": [ "ia32" ], @@ -1122,9 +1124,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", "cpu": [ "loong64" ], @@ -1139,9 +1141,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", "cpu": [ "mips64el" ], @@ -1156,9 +1158,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", "cpu": [ "ppc64" ], @@ -1173,9 +1175,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", "cpu": [ "riscv64" ], @@ -1190,9 +1192,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", "cpu": [ "s390x" ], @@ -1207,9 +1209,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", "cpu": [ "x64" ], @@ -1224,9 +1226,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", "cpu": [ "arm64" ], @@ -1241,9 +1243,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ "x64" ], @@ -1258,9 +1260,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", "cpu": [ "arm64" ], @@ -1275,9 +1277,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ "x64" ], @@ -1292,9 +1294,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", "cpu": [ "arm64" ], @@ -1309,9 +1311,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], @@ -1326,9 +1328,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ "arm64" ], @@ -1343,9 +1345,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", "cpu": [ "ia32" ], @@ -1360,9 +1362,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", "cpu": [ "x64" ], @@ -1419,37 +1421,37 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", - "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^3.0.2", + "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", - "minimatch": "^10.2.1" + "minimatch": "^10.2.4" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/config-helpers": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", - "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.1.0" + "@eslint/core": "^1.2.1" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", - "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1481,9 +1483,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", - "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1491,13 +1493,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", - "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.1.0", + "@eslint/core": "^1.2.1", "levn": "^0.4.1" }, "engines": { @@ -1658,17 +1660,17 @@ } }, "node_modules/@jest/console": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", - "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", + "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", "slash": "^3.0.0" }, "engines": { @@ -1676,39 +1678,38 @@ } }, "node_modules/@jest/core": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", - "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", + "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.2.0", + "@jest/console": "30.3.0", "@jest/pattern": "30.0.1", - "@jest/reporters": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/reporters": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.2.0", - "jest-config": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", + "jest-changed-files": "30.3.0", + "jest-config": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-resolve-dependencies": "30.2.0", - "jest-runner": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "jest-watcher": "30.2.0", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", + "jest-resolve": "30.3.0", + "jest-resolve-dependencies": "30.3.0", + "jest-runner": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "jest-watcher": "30.3.0", + "pretty-format": "30.3.0", "slash": "^3.0.0" }, "engines": { @@ -1724,44 +1725,45 @@ } }, "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/environment": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", - "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", + "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "30.2.0" + "jest-mock": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/environment-jsdom-abstract": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.2.0.tgz", - "integrity": "sha512-kazxw2L9IPuZpQ0mEt9lu9Z98SqR74xcagANmMBU16X0lS23yPc0+S6hGLUz8kVRlomZEs/5S/Zlpqwf5yu6OQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.3.0.tgz", + "integrity": "sha512-0hNFs5N6We3DMCwobzI0ydhkY10sT1tZSC0AAiy+0g2Dt/qEWgrcV5BrMxPczhe41cxW4qm6X+jqZaUdpZIajA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/jsdom": "^21.1.7", "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1777,23 +1779,24 @@ } }, "node_modules/@jest/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.2.0", - "jest-snapshot": "30.2.0" + "expect": "30.3.0", + "jest-snapshot": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", - "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0" @@ -1803,18 +1806,18 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", - "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", + "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", - "@sinonjs/fake-timers": "^13.0.0", + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", "@types/node": "*", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1824,22 +1827,23 @@ "version": "30.1.0", "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/globals": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", - "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", + "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/types": "30.2.0", - "jest-mock": "30.2.0" + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1849,6 +1853,7 @@ "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -1859,32 +1864,32 @@ } }, "node_modules/@jest/reporters": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", - "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", + "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/console": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", "collect-v8-coverage": "^1.0.2", "exit-x": "^0.2.2", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -1905,6 +1910,7 @@ "version": "30.0.5", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -1914,13 +1920,13 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", - "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", + "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -1945,14 +1951,14 @@ } }, "node_modules/@jest/test-result": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", - "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", + "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.2.0", - "@jest/types": "30.2.0", + "@jest/console": "30.3.0", + "@jest/types": "30.3.0", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -1961,15 +1967,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", - "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", + "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.2.0", + "@jest/test-result": "30.3.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", + "jest-haste-map": "30.3.0", "slash": "^3.0.0" }, "engines": { @@ -1977,24 +1983,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", - "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@jridgewell/trace-mapping": "^0.3.25", "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", + "jest-haste-map": "30.3.0", "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "micromatch": "^4.0.8", + "jest-util": "30.3.0", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" @@ -2004,9 +2009,10 @@ } }, "node_modules/@jest/types": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", - "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/pattern": "30.0.1", @@ -2072,15 +2078,15 @@ } }, "node_modules/@lezer/common": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz", - "integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", "license": "MIT" }, "node_modules/@lezer/css": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.1.tgz", - "integrity": "sha512-PYAKeUVBo3HFThruRyp/iK91SwiZJnzXh8QzkQlwijB5y+N5iB28+iLk78o2zmKqqV0uolNhCwFqB8LA7b0Svg==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.3.tgz", + "integrity": "sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -2333,6 +2339,9 @@ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2354,6 +2363,9 @@ "arm" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2375,6 +2387,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2396,6 +2411,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2417,6 +2435,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2438,6 +2459,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2515,9 +2539,9 @@ } }, "node_modules/@parcel/watcher/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "optional": true, @@ -2553,9 +2577,10 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, "license": "MIT" }, "node_modules/@sinonjs/commons": { @@ -2569,9 +2594,9 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.3.0.tgz", + "integrity": "sha512-m2xozxSfCIxjDdvbhIWazlP2i2aha/iUmbl94alpsIbd3iLTfeXgfBVbwyWogB6l++istyGZqamgA/EcqYf+Bg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2697,12 +2722,14 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" @@ -2712,6 +2739,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" @@ -2721,6 +2749,7 @@ "version": "30.0.0", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, "license": "MIT", "dependencies": { "expect": "^30.0.0", @@ -2772,9 +2801,10 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.3.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz", - "integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==", + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.18.0" @@ -2791,6 +2821,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, "license": "MIT" }, "node_modules/@types/tough-cookie": { @@ -2804,6 +2835,7 @@ "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -2813,6 +2845,7 @@ "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, "license": "MIT" }, "node_modules/@ungap/structured-clone": { @@ -2928,6 +2961,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2942,6 +2978,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2956,6 +2995,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2970,6 +3012,9 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2984,6 +3029,9 @@ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2998,6 +3046,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3012,6 +3063,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3026,6 +3080,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3187,6 +3244,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3295,16 +3353,16 @@ } }, "node_modules/babel-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", - "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", + "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.2.0", + "@jest/transform": "30.3.0", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.2.0", + "babel-preset-jest": "30.3.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -3337,9 +3395,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", - "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", + "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", "dev": true, "license": "MIT", "dependencies": { @@ -3377,13 +3435,13 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", - "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", + "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.2.0", + "babel-plugin-jest-hoist": "30.3.0", "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { @@ -3404,9 +3462,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "version": "2.10.16", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz", + "integrity": "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3430,9 +3488,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3446,6 +3504,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -3455,9 +3514,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -3475,11 +3534,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -3589,9 +3648,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001777", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", - "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", + "version": "1.0.30001787", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", + "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", "dev": true, "funding": [ { @@ -3613,6 +3672,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -3683,6 +3743,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, "funding": [ { "type": "github", @@ -3850,6 +3911,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3862,6 +3924,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/concat-map": { @@ -4145,9 +4208,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.307", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", - "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", + "version": "1.5.334", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.334.tgz", + "integrity": "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==", "dev": true, "license": "ISC" }, @@ -4194,9 +4257,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -4330,9 +4393,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4343,32 +4406,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/escalade": { @@ -4395,18 +4458,18 @@ } }, "node_modules/eslint": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz", - "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz", + "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.2", - "@eslint/config-helpers": "^0.5.2", - "@eslint/core": "^1.1.0", - "@eslint/plugin-kit": "^0.6.0", + "@eslint/config-array": "^0.23.4", + "@eslint/config-helpers": "^0.5.4", + "@eslint/core": "^1.2.0", + "@eslint/plugin-kit": "^0.7.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -4415,9 +4478,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.1", + "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", - "espree": "^11.1.1", + "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -4428,7 +4491,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.1", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -4451,9 +4514,9 @@ } }, "node_modules/eslint-scope": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", - "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -4496,9 +4559,9 @@ } }, "node_modules/espree": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", - "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -4615,17 +4678,18 @@ } }, "node_modules/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", + "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.2.0", + "@jest/expect-utils": "30.3.0", "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -4679,6 +4743,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -4719,9 +4784,9 @@ } }, "node_modules/flatted": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", - "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -4974,9 +5039,9 @@ "license": "MIT" }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -5046,12 +5111,13 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5087,6 +5153,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5592,6 +5659,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -5894,16 +5962,16 @@ } }, "node_modules/jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", - "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", + "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.2.0", - "@jest/types": "30.2.0", + "@jest/core": "30.3.0", + "@jest/types": "30.3.0", "import-local": "^3.2.0", - "jest-cli": "30.2.0" + "jest-cli": "30.3.0" }, "bin": { "jest": "bin/jest.js" @@ -5921,14 +5989,14 @@ } }, "node_modules/jest-changed-files": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", - "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", + "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.2.0", + "jest-util": "30.3.0", "p-limit": "^3.1.0" }, "engines": { @@ -5936,29 +6004,29 @@ } }, "node_modules/jest-circus": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", - "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", + "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", "p-limit": "^3.1.0", - "pretty-format": "30.2.0", + "pretty-format": "30.3.0", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -5968,21 +6036,21 @@ } }, "node_modules/jest-cli": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", - "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", + "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/core": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", + "jest-config": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "yargs": "^17.7.2" }, "bin": { @@ -6108,34 +6176,33 @@ } }, "node_modules/jest-config": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", - "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", + "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.1.0", "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.2.0", - "@jest/types": "30.2.0", - "babel-jest": "30.2.0", + "@jest/test-sequencer": "30.3.0", + "@jest/types": "30.3.0", + "babel-jest": "30.3.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-circus": "30.2.0", + "jest-circus": "30.3.0", "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", + "jest-environment-node": "30.3.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-runner": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "micromatch": "^4.0.8", + "jest-resolve": "30.3.0", + "jest-runner": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "parse-json": "^5.2.0", - "pretty-format": "30.2.0", + "pretty-format": "30.3.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -6160,15 +6227,16 @@ } }, "node_modules/jest-diff": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.0.1", + "@jest/diff-sequences": "30.3.0", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.2.0" + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6188,33 +6256,31 @@ } }, "node_modules/jest-each": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", - "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", + "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "chalk": "^4.1.2", - "jest-util": "30.2.0", - "pretty-format": "30.2.0" + "jest-util": "30.3.0", + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-jsdom": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.2.0.tgz", - "integrity": "sha512-zbBTiqr2Vl78pKp/laGBREYzbZx9ZtqPjOK4++lL4BNDhxRnahg51HtoDrk9/VjIy9IthNEWdKVd7H5bqBhiWQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.3.0.tgz", + "integrity": "sha512-RLEOJy6ip1lpw0yqJ8tB3i88FC7VBz7i00Zvl2qF71IdxjS98gC9/0SPWYIBVXHm5hgCYK0PAlSlnHGGy9RoMg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/environment-jsdom-abstract": "30.2.0", - "@types/jsdom": "^21.1.7", - "@types/node": "*", + "@jest/environment": "30.3.0", + "@jest/environment-jsdom-abstract": "30.3.0", "jsdom": "^26.1.0" }, "engines": { @@ -6230,40 +6296,40 @@ } }, "node_modules/jest-environment-node": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", - "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", + "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0" + "jest-mock": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", - "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "micromatch": "^4.0.8", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", "walker": "^1.0.8" }, "engines": { @@ -6273,48 +6339,63 @@ "fsevents": "^2.3.3" } }, + "node_modules/jest-haste-map/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-leak-detector": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", - "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", + "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "pretty-format": "30.2.0" + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", - "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "jest-diff": "30.2.0", - "pretty-format": "30.2.0" + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", - "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -6322,15 +6403,29 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-message-util/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-mock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-util": "30.2.0" + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6358,24 +6453,25 @@ "version": "30.0.1", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", - "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", + "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", + "jest-haste-map": "30.3.0", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -6384,46 +6480,46 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", - "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", + "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", "dev": true, "license": "MIT", "dependencies": { "jest-regex-util": "30.0.1", - "jest-snapshot": "30.2.0" + "jest-snapshot": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", - "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", + "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.2.0", - "@jest/environment": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/console": "30.3.0", + "@jest/environment": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-leak-detector": "30.2.0", - "jest-message-util": "30.2.0", - "jest-resolve": "30.2.0", - "jest-runtime": "30.2.0", - "jest-util": "30.2.0", - "jest-watcher": "30.2.0", - "jest-worker": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-leak-detector": "30.3.0", + "jest-message-util": "30.3.0", + "jest-resolve": "30.3.0", + "jest-runtime": "30.3.0", + "jest-util": "30.3.0", + "jest-watcher": "30.3.0", + "jest-worker": "30.3.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -6432,32 +6528,32 @@ } }, "node_modules/jest-runtime": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", - "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", + "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/globals": "30.2.0", + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/globals": "30.3.0", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", + "jest-resolve": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -6466,9 +6562,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", - "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", + "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6477,20 +6573,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.2.0", + "@jest/expect-utils": "30.3.0", "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/snapshot-utils": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.2.0", + "expect": "30.3.0", "graceful-fs": "^4.2.11", - "jest-diff": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "pretty-format": "30.2.0", + "jest-diff": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "pretty-format": "30.3.0", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -6512,26 +6608,28 @@ } }, "node_modules/jest-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", - "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" + "picomatch": "^4.0.3" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -6541,18 +6639,18 @@ } }, "node_modules/jest-validate": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", - "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.2.0" + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6572,19 +6670,19 @@ } }, "node_modules/jest-watcher": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", - "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", + "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.2.0", + "jest-util": "30.3.0", "string-length": "^4.0.2" }, "engines": { @@ -6592,15 +6690,15 @@ } }, "node_modules/jest-worker": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", - "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.2.0", + "jest-util": "30.3.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -6628,6 +6726,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -6989,19 +7088,6 @@ "dev": true, "license": "MIT" }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -7013,13 +7099,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -7108,9 +7194,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", "dev": true, "license": "MIT" }, @@ -7194,9 +7280,9 @@ "license": "MIT" }, "node_modules/npm-run-all/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -7653,12 +7739,14 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -7790,9 +7878,10 @@ } }, "node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "30.0.5", @@ -7807,6 +7896,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -7855,6 +7945,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, "license": "MIT" }, "node_modules/read-pkg": { @@ -8060,14 +8151,14 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.97.3", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", - "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.99.0.tgz", + "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==", "dev": true, "license": "MIT", "dependencies": { "chokidar": "^4.0.0", - "immutable": "^5.0.2", + "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -8246,14 +8337,14 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -8318,6 +8409,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8416,6 +8508,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" @@ -8428,6 +8521,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8709,6 +8803,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -8776,9 +8871,9 @@ "license": "MIT" }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -8852,6 +8947,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -8887,19 +8983,19 @@ } }, "node_modules/ts-jest": { - "version": "29.4.6", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", - "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "version": "29.4.9", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", + "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.8", + "handlebars": "^4.7.9", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.7.3", + "semver": "^7.7.4", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, @@ -8916,7 +9012,7 @@ "babel-jest": "^29.0.0 || ^30.0.0", "jest": "^29.0.0 || ^30.0.0", "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <6" + "typescript": ">=4.3 <7" }, "peerDependenciesMeta": { "@babel/core": { @@ -9132,9 +9228,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -9188,6 +9284,7 @@ "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -9622,9 +9719,9 @@ } }, "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 45c275863b3..80810fff170 100644 --- a/package.json +++ b/package.json @@ -20,22 +20,23 @@ "devDependencies": { "@eslint/js": "^10.0.1", "@lezer/generator": "^1.8.0", + "@types/jest": "^30.0.0", "@types/markdown-it": "^14.1.2", "@types/sortablejs": "^1.15.9", "chokidar-cli": "^3.0", - "esbuild": "^0.27.3", - "eslint": "^10.0.2", + "esbuild": "^0.28.0", + "eslint": "^10.2.0", "globals": "^17.4.0", - "jest": "^30.2.0", - "jest-environment-jsdom": "^30.2.0", + "jest": "^30.3.0", + "jest-environment-jsdom": "^30.3.0", "npm-run-all": "^4.1.5", - "sass": "^1.97.3", - "ts-jest": "^29.4.6", + "sass": "^1.99.0", + "ts-jest": "^29.4.9", "ts-node": "^10.9.2", - "typescript": "5.9.*" + "typescript": "6.0.*" }, "dependencies": { - "@codemirror/commands": "^6.10.2", + "@codemirror/commands": "^6.10.3", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.11", "@codemirror/lang-javascript": "^6.2.5", @@ -43,15 +44,14 @@ "@codemirror/lang-markdown": "^6.5.0", "@codemirror/lang-php": "^6.0.2", "@codemirror/lang-xml": "^6.1.0", - "@codemirror/language": "^6.12.2", + "@codemirror/language": "^6.12.3", "@codemirror/legacy-modes": "^6.5.2", - "@codemirror/state": "^6.5.4", + "@codemirror/state": "^6.6.0", "@codemirror/theme-one-dark": "^6.1.3", - "@codemirror/view": "^6.39.16", + "@codemirror/view": "^6.41.0", "@lezer/highlight": "^1.2.3", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", - "@types/jest": "^30.0.0", "codemirror": "^6.0.2", "idb-keyval": "^6.2.2", "markdown-it": "^14.1.1", diff --git a/tsconfig.json b/tsconfig.json index dacaefea279..55b61674cdb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,15 +2,16 @@ "include": ["resources/js/**/*"], "exclude": ["resources/js/wysiwyg/lexical/yjs/*"], "compilerOptions": { - "target": "es2022", - "module": "commonjs", + "target": "es2023", + "module": "esnext", + "moduleResolution": "bundler", "rootDir": "./resources/js/", - "baseUrl": "./", + "types": ["jest", "node"], "paths": { - "@icons/*": ["resources/icons/*"], - "lexical": ["resources/js/wysiwyg/lexical/core/index.ts"], - "lexical/*": ["resources/js/wysiwyg/lexical/core/*"], - "@lexical/*": ["resources/js/wysiwyg/lexical/*"] + "@icons/*": ["./resources/icons/*"], + "lexical": ["./resources/js/wysiwyg/lexical/core/index.ts"], + "lexical/*": ["./resources/js/wysiwyg/lexical/core/*"], + "@lexical/*": ["./resources/js/wysiwyg/lexical/*"] }, "resolveJsonModule": true, "allowJs": true, From 5e78dc6ed54aeb6d3d2565334074286a0e249b78 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 8 Apr 2026 21:03:20 +0100 Subject: [PATCH 101/204] Maintenance: Updated PHPStan to Level 4 (#6085) --- .gitignore | 1 + app/Access/EmailConfirmationService.php | 2 ++ app/Access/LoginService.php | 2 +- app/Access/Mfa/MfaValue.php | 5 ++- app/Access/Oidc/OidcJwtSigningKey.php | 17 ++++------ app/Access/Oidc/OidcJwtWithClaims.php | 4 +-- app/Access/Oidc/OidcUserDetails.php | 2 +- app/Access/Saml2Service.php | 4 +-- app/Access/SocialAuthService.php | 4 +-- .../Notifications/NotificationManager.php | 14 ++++---- app/Api/ApiDocsGenerator.php | 10 ++++-- app/Api/ApiEntityListFormatter.php | 7 ++-- app/Api/ApiTokenGuard.php | 33 +++++-------------- .../Commands/AssignSortRuleCommand.php | 2 +- .../Commands/CopyShelfPermissionsCommand.php | 17 ++++++---- app/Entities/Models/Book.php | 2 +- app/Entities/Models/Entity.php | 1 + app/Entities/Models/Page.php | 2 +- app/Entities/Repos/PageRepo.php | 2 +- app/Entities/Tools/PageContent.php | 2 +- app/Entities/Tools/PermissionsUpdater.php | 2 +- app/Exports/ExportFormatter.php | 4 +-- app/Exports/ZipExports/ZipImportRunner.php | 14 ++++---- app/Exports/ZipExports/ZipReferenceParser.php | 4 --- app/Http/Controller.php | 2 +- app/Permissions/JointPermissionBuilder.php | 3 +- app/Search/SearchOptions.php | 12 +++---- app/Sorting/BookSorter.php | 3 +- app/Uploads/ImageRepo.php | 2 +- app/Uploads/UserAvatars.php | 2 +- app/Users/Models/User.php | 3 +- phpstan.neon.dist | 4 +-- tests/Api/SearchApiTest.php | 1 + .../CopyShelfPermissionsCommandTest.php | 18 ++++++++++ 34 files changed, 105 insertions(+), 102 deletions(-) diff --git a/.gitignore b/.gitignore index b545d161f13..06a8723c5c1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /node_modules /.vscode /composer +/composer.phar /coverage Homestead.yaml .env diff --git a/app/Access/EmailConfirmationService.php b/app/Access/EmailConfirmationService.php index 1a5156d3e7a..e950c5504b7 100644 --- a/app/Access/EmailConfirmationService.php +++ b/app/Access/EmailConfirmationService.php @@ -5,6 +5,7 @@ use BookStack\Access\Notifications\ConfirmEmailNotification; use BookStack\Exceptions\ConfirmationEmailException; use BookStack\Users\Models\User; +use Exception; class EmailConfirmationService extends UserTokenService { @@ -16,6 +17,7 @@ class EmailConfirmationService extends UserTokenService * Also removes any existing old ones. * * @throws ConfirmationEmailException + * @throws Exception */ public function sendConfirmation(User $user): void { diff --git a/app/Access/LoginService.php b/app/Access/LoginService.php index c81e955722c..0c32b538f28 100644 --- a/app/Access/LoginService.php +++ b/app/Access/LoginService.php @@ -71,7 +71,7 @@ public function reattemptLoginFor(User $user): void } $lastLoginDetails = $this->getLastLoginAttemptDetails(); - $this->login($user, $lastLoginDetails['method'], $lastLoginDetails['remember'] ?? false); + $this->login($user, $lastLoginDetails['method'], $lastLoginDetails['remember']); } /** diff --git a/app/Access/Mfa/MfaValue.php b/app/Access/Mfa/MfaValue.php index dd3e04618f7..b0f08e82684 100644 --- a/app/Access/Mfa/MfaValue.php +++ b/app/Access/Mfa/MfaValue.php @@ -48,17 +48,16 @@ public static function upsertWithValue(User $user, string $method, string $value } /** - * Easily get the decrypted MFA value for the given user and method. + * Get the decrypted MFA value for the given user and method. */ public static function getValueForUser(User $user, string $method): ?string { - /** @var MfaValue $mfaVal */ $mfaVal = static::query() ->where('user_id', '=', $user->id) ->where('method', '=', $method) ->first(); - return $mfaVal ? $mfaVal->getValue() : null; + return $mfaVal?->getValue(); } /** diff --git a/app/Access/Oidc/OidcJwtSigningKey.php b/app/Access/Oidc/OidcJwtSigningKey.php index 3dab3e44275..3edba12b36f 100644 --- a/app/Access/Oidc/OidcJwtSigningKey.php +++ b/app/Access/Oidc/OidcJwtSigningKey.php @@ -9,10 +9,7 @@ class OidcJwtSigningKey { - /** - * @var PublicKey - */ - protected $key; + protected PublicKey $key; /** * Can be created either from a JWK parameter array or local file path to load a certificate from. @@ -20,15 +17,13 @@ class OidcJwtSigningKey * 'file:///var/www/cert.pem' * ['kty' => 'RSA', 'alg' => 'RS256', 'n' => 'abc123...']. * - * @param array|string $jwkOrKeyPath - * * @throws OidcInvalidKeyException */ - public function __construct($jwkOrKeyPath) + public function __construct(array|string $jwkOrKeyPath) { if (is_array($jwkOrKeyPath)) { $this->loadFromJwkArray($jwkOrKeyPath); - } elseif (is_string($jwkOrKeyPath) && strpos($jwkOrKeyPath, 'file://') === 0) { + } elseif (str_starts_with($jwkOrKeyPath, 'file://')) { $this->loadFromPath($jwkOrKeyPath); } else { throw new OidcInvalidKeyException('Unexpected type of key value provided'); @@ -38,7 +33,7 @@ public function __construct($jwkOrKeyPath) /** * @throws OidcInvalidKeyException */ - protected function loadFromPath(string $path) + protected function loadFromPath(string $path): void { try { $key = PublicKeyLoader::load( @@ -58,7 +53,7 @@ protected function loadFromPath(string $path) /** * @throws OidcInvalidKeyException */ - protected function loadFromJwkArray(array $jwk) + protected function loadFromJwkArray(array $jwk): void { // 'alg' is optional for a JWK, but we will still attempt to validate if // it exists otherwise presume it will be compatible. @@ -82,7 +77,7 @@ protected function loadFromJwkArray(array $jwk) throw new OidcInvalidKeyException('A "n" parameter on the provided key is expected'); } - $n = strtr($jwk['n'] ?? '', '-_', '+/'); + $n = strtr($jwk['n'], '-_', '+/'); try { $key = PublicKeyLoader::load([ diff --git a/app/Access/Oidc/OidcJwtWithClaims.php b/app/Access/Oidc/OidcJwtWithClaims.php index 06c04d81eb7..9d7eeead1a9 100644 --- a/app/Access/Oidc/OidcJwtWithClaims.php +++ b/app/Access/Oidc/OidcJwtWithClaims.php @@ -102,12 +102,12 @@ public function replaceClaims(array $claims): void protected function validateTokenStructure(): void { foreach (['header', 'payload'] as $prop) { - if (empty($this->$prop) || !is_array($this->$prop)) { + if (empty($this->$prop)) { throw new OidcInvalidTokenException("Could not parse out a valid {$prop} within the provided token"); } } - if (empty($this->signature) || !is_string($this->signature)) { + if (empty($this->signature)) { throw new OidcInvalidTokenException('Could not parse out a valid signature within the provided token'); } } diff --git a/app/Access/Oidc/OidcUserDetails.php b/app/Access/Oidc/OidcUserDetails.php index 7a422a58de2..b1736f97d31 100644 --- a/app/Access/Oidc/OidcUserDetails.php +++ b/app/Access/Oidc/OidcUserDetails.php @@ -39,7 +39,7 @@ public function populate( ): void { $this->externalId = $claims->getClaim($idClaim) ?? $this->externalId; $this->email = $claims->getClaim('email') ?? $this->email; - $this->name = static::getUserDisplayName($displayNameClaims, $claims) ?? $this->name; + $this->name = static::getUserDisplayName($displayNameClaims, $claims) ?: $this->name; $this->groups = static::getUserGroups($groupsClaim, $claims) ?? $this->groups; $this->picture = static::getPicture($claims) ?: $this->picture; } diff --git a/app/Access/Saml2Service.php b/app/Access/Saml2Service.php index 106a7a22906..5572d210401 100644 --- a/app/Access/Saml2Service.php +++ b/app/Access/Saml2Service.php @@ -266,7 +266,7 @@ protected function getExternalId(array $samlAttributes, string $defaultValue) /** * Extract the details of a user from a SAML response. * - * @return array{external_id: string, name: string, email: string, saml_id: string} + * @return array{external_id: string, name: string, email: string|null, saml_id: string} */ protected function getUserDetails(string $samlID, $samlAttributes): array { @@ -357,7 +357,7 @@ public function processLoginCallback(string $samlID, array $samlAttributes): Use ]); } - if ($userDetails['email'] === null) { + if (empty($userDetails['email'])) { throw new SamlException(trans('errors.saml_no_email_address')); } diff --git a/app/Access/SocialAuthService.php b/app/Access/SocialAuthService.php index c3c20587db3..bdcfb45c865 100644 --- a/app/Access/SocialAuthService.php +++ b/app/Access/SocialAuthService.php @@ -117,14 +117,14 @@ public function handleLoginCallback(string $socialDriver, SocialUser $socialUser } // When a user is logged in and the social account exists and is already linked to the current user. - if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id === $currentUser->id) { + if ($isLoggedIn && $socialAccount->user->id === $currentUser->id) { session()->flash('error', trans('errors.social_account_existing', ['socialAccount' => $titleCaseDriver])); return redirect('/my-account/auth#social_accounts'); } // When a user is logged in, A social account exists but the users do not match. - if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id != $currentUser->id) { + if ($isLoggedIn && $socialAccount->user->id != $currentUser->id) { session()->flash('error', trans('errors.social_account_already_used_existing', ['socialAccount' => $titleCaseDriver])); return redirect('/my-account/auth#social_accounts'); diff --git a/app/Activity/Notifications/NotificationManager.php b/app/Activity/Notifications/NotificationManager.php index 8a6c26ffbed..38da2c552a5 100644 --- a/app/Activity/Notifications/NotificationManager.php +++ b/app/Activity/Notifications/NotificationManager.php @@ -15,14 +15,14 @@ class NotificationManager { /** - * @var class-string[] + * @var array[]> */ - protected array $handlers = []; + protected array $handlersByActivity = []; public function handle(Activity $activity, string|Loggable $detail, User $user): void { $activityType = $activity->type; - $handlersToRun = $this->handlers[$activityType] ?? []; + $handlersToRun = $this->handlersByActivity[$activityType] ?? []; foreach ($handlersToRun as $handlerClass) { /** @var NotificationHandler $handler */ $handler = new $handlerClass(); @@ -35,12 +35,12 @@ public function handle(Activity $activity, string|Loggable $detail, User $user): */ public function registerHandler(string $activityType, string $handlerClass): void { - if (!isset($this->handlers[$activityType])) { - $this->handlers[$activityType] = []; + if (!isset($this->handlersByActivity[$activityType])) { + $this->handlersByActivity[$activityType] = []; } - if (!in_array($handlerClass, $this->handlers[$activityType])) { - $this->handlers[$activityType][] = $handlerClass; + if (!in_array($handlerClass, $this->handlersByActivity[$activityType])) { + $this->handlersByActivity[$activityType][] = $handlerClass; } } diff --git a/app/Api/ApiDocsGenerator.php b/app/Api/ApiDocsGenerator.php index eb8f5508c70..a59cb8198e2 100644 --- a/app/Api/ApiDocsGenerator.php +++ b/app/Api/ApiDocsGenerator.php @@ -17,7 +17,14 @@ class ApiDocsGenerator { + /** + * @var array + */ protected array $reflectionClasses = []; + + /** + * @var array + */ protected array $controllerClasses = []; /** @@ -107,7 +114,6 @@ protected function loadDetailsFromControllers(Collection $routes): Collection */ protected function getBodyParamsFromClass(string $className, string $methodName): ?array { - /** @var ApiController $class */ $class = $this->controllerClasses[$className] ?? null; if ($class === null) { $class = app()->make($className); @@ -153,7 +159,7 @@ protected function parseDescriptionFromDocBlockComment(string $comment): string $matches = []; preg_match_all('/^\s*?\*\s?($|((?![\/@\s]).*?))$/m', $comment, $matches); - $text = implode(' ', $matches[1] ?? []); + $text = implode(' ', $matches[1]); return str_replace(' ', "\n", $text); } diff --git a/app/Api/ApiEntityListFormatter.php b/app/Api/ApiEntityListFormatter.php index 3c94d96ee60..23073bfc2fd 100644 --- a/app/Api/ApiEntityListFormatter.php +++ b/app/Api/ApiEntityListFormatter.php @@ -74,18 +74,21 @@ public function withTags(): self /** * Include parent book/chapter info in the formatted data. + * These functions are careful to not load the relation themselves, since they should + * have already been loaded in a more efficient manner, with permissions applied, by the time + * the parent fields are handled here. */ public function withParents(): self { $this->withField('book', function (Entity $entity) { - if ($entity instanceof BookChild && $entity->book) { + if ($entity instanceof BookChild && $entity->relationLoaded('book') && $entity->getRelationValue('book')) { return $entity->book->only(['id', 'name', 'slug']); } return null; }); $this->withField('chapter', function (Entity $entity) { - if ($entity instanceof Page && $entity->chapter) { + if ($entity instanceof Page && $entity->relationLoaded('chapter') && $entity->getRelationValue('chapter')) { return $entity->chapter->only(['id', 'name', 'slug']); } return null; diff --git a/app/Api/ApiTokenGuard.php b/app/Api/ApiTokenGuard.php index 9f4537b296b..f1a3f0dc883 100644 --- a/app/Api/ApiTokenGuard.php +++ b/app/Api/ApiTokenGuard.php @@ -16,30 +16,15 @@ class ApiTokenGuard implements Guard { use GuardHelpers; - /** - * The request instance. - */ - protected $request; - - /** - * @var LoginService - */ - protected $loginService; - /** * The last auth exception thrown in this request. - * - * @var ApiAuthException */ - protected $lastAuthException; + protected ApiAuthException|null $lastAuthException = null; - /** - * ApiTokenGuard constructor. - */ - public function __construct(Request $request, LoginService $loginService) - { - $this->request = $request; - $this->loginService = $loginService; + public function __construct( + protected Request $request, + protected LoginService $loginService + ) { } /** @@ -67,7 +52,7 @@ public function user() } /** - * Determine if current user is authenticated. If not, throw an exception. + * Determine if the current user is authenticated. If not, throw an exception. * * @throws ApiAuthException * @@ -121,7 +106,7 @@ protected function validateTokenHeaderValue(string $authToken): void throw new ApiAuthException(trans('errors.api_no_authorization_found')); } - if (strpos($authToken, ':') === false || strpos($authToken, 'Token ') !== 0) { + if (!str_contains($authToken, ':') || !str_starts_with($authToken, 'Token ')) { throw new ApiAuthException(trans('errors.api_bad_authorization_format')); } } @@ -155,7 +140,7 @@ protected function validateToken(?ApiToken $token, string $secret): void /** * {@inheritdoc} */ - public function validate(array $credentials = []) + public function validate(array $credentials = []): bool { if (empty($credentials['id']) || empty($credentials['secret'])) { return false; @@ -175,7 +160,7 @@ public function validate(array $credentials = []) /** * "Log out" the currently authenticated user. */ - public function logout() + public function logout(): void { $this->user = null; } diff --git a/app/Console/Commands/AssignSortRuleCommand.php b/app/Console/Commands/AssignSortRuleCommand.php index c438d078326..f00df83831c 100644 --- a/app/Console/Commands/AssignSortRuleCommand.php +++ b/app/Console/Commands/AssignSortRuleCommand.php @@ -32,7 +32,7 @@ class AssignSortRuleCommand extends Command */ public function handle(BookSorter $sorter): int { - $sortRuleId = intval($this->argument('sort-rule')) ?? 0; + $sortRuleId = intval($this->argument('sort-rule')); if ($sortRuleId === 0) { return $this->listSortRules(); } diff --git a/app/Console/Commands/CopyShelfPermissionsCommand.php b/app/Console/Commands/CopyShelfPermissionsCommand.php index c5e2d504e75..1207621debc 100644 --- a/app/Console/Commands/CopyShelfPermissionsCommand.php +++ b/app/Console/Commands/CopyShelfPermissionsCommand.php @@ -32,6 +32,7 @@ public function handle(PermissionsUpdater $permissionsUpdater, BookshelfQueries { $shelfSlug = $this->option('slug'); $cascadeAll = $this->option('all'); + $noInteraction = boolval($this->option('no-interaction')); $shelves = null; if (!$cascadeAll && !$shelfSlug) { @@ -41,14 +42,16 @@ public function handle(PermissionsUpdater $permissionsUpdater, BookshelfQueries } if ($cascadeAll) { - $continue = $this->confirm( - 'Permission settings for all shelves will be cascaded. ' . - 'Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. ' . - 'Are you sure you want to proceed?' - ); + if (!$noInteraction) { + $continue = $this->confirm( + 'Permission settings for all shelves will be cascaded. ' . + 'Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. ' . + 'Are you sure you want to proceed?', + ); - if (!$continue && !$this->hasOption('no-interaction')) { - return 0; + if (!$continue) { + return 0; + } } $shelves = $queries->start()->get(['id']); diff --git a/app/Entities/Models/Book.php b/app/Entities/Models/Book.php index 1909dbd5631..10f04695a5e 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -17,7 +17,7 @@ * * @property string $description * @property string $description_html - * @property int $image_id + * @property ?int $image_id * @property ?int $default_template_id * @property ?int $sort_rule_id * @property \Illuminate\Database\Eloquent\Collection $chapters diff --git a/app/Entities/Models/Entity.php b/app/Entities/Models/Entity.php index 47e13462691..27cfccaa836 100644 --- a/app/Entities/Models/Entity.php +++ b/app/Entities/Models/Entity.php @@ -479,6 +479,7 @@ public static function instanceFromType(string $type): self 'chapter' => new Chapter(), 'book' => new Book(), 'bookshelf' => new Bookshelf(), + default => throw new \InvalidArgumentException("Invalid entity type: {$type}"), }; } } diff --git a/app/Entities/Models/Page.php b/app/Entities/Models/Page.php index a1d3fc7b40d..d3a392da6fa 100644 --- a/app/Entities/Models/Page.php +++ b/app/Entities/Models/Page.php @@ -23,7 +23,7 @@ * @property bool $draft * @property int $revision_count * @property string $editor - * @property Chapter $chapter + * @property Chapter|null $chapter * @property Collection $attachments * @property Collection $revisions * @property PageRevision $currentRevision diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index bc590785d93..375bf1d2bc1 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -60,7 +60,7 @@ public function getNewDraftPage(Entity $parent): Page $page->book_id = $parent->id; } - $defaultTemplate = $page->chapter?->defaultTemplate()->get() ?? $page->book?->defaultTemplate()->get(); + $defaultTemplate = $page->chapter?->defaultTemplate()->get() ?? $page->book->defaultTemplate()->get(); if ($defaultTemplate) { $page->forceFill([ 'html' => $defaultTemplate->html, diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index 8d89a86cff4..6a764990079 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -359,7 +359,7 @@ protected function getContentCacheKey(string $html): string { $contentHash = md5($html); $contentId = $this->page->id; - $contentTime = $this->page->updated_at?->timestamp ?? time(); + $contentTime = $this->page->updated_at->timestamp ?? time(); $appVersion = AppVersion::get(); $filterConfig = config('app.content_filtering') ?? ''; return "page-content-cache::{$filterConfig}::{$appVersion}::{$contentId}::{$contentTime}::{$contentHash}"; diff --git a/app/Entities/Tools/PermissionsUpdater.php b/app/Entities/Tools/PermissionsUpdater.php index fa9ae753c51..f3165b603e5 100644 --- a/app/Entities/Tools/PermissionsUpdater.php +++ b/app/Entities/Tools/PermissionsUpdater.php @@ -47,7 +47,7 @@ public function updateFromApiRequestData(Entity $entity, array $data): void { if (isset($data['role_permissions'])) { $entity->permissions()->where('role_id', '!=', 0)->delete(); - $rolePermissionData = $this->formatPermissionsFromApiRequestToEntityPermissions($data['role_permissions'] ?? [], false); + $rolePermissionData = $this->formatPermissionsFromApiRequestToEntityPermissions($data['role_permissions'], false); $entity->permissions()->createMany($rolePermissionData); } diff --git a/app/Exports/ExportFormatter.php b/app/Exports/ExportFormatter.php index c5973eace29..6bf0a05add9 100644 --- a/app/Exports/ExportFormatter.php +++ b/app/Exports/ExportFormatter.php @@ -208,7 +208,7 @@ protected function containHtml(string $htmlContent): string preg_match_all("/\/i", $htmlContent, $imageTagsOutput); // Replace image src with base64 encoded image strings - if (isset($imageTagsOutput[0]) && count($imageTagsOutput[0]) > 0) { + if (count($imageTagsOutput[0]) > 0) { foreach ($imageTagsOutput[0] as $index => $imgMatch) { $oldImgTagString = $imgMatch; $srcString = $imageTagsOutput[2][$index]; @@ -225,7 +225,7 @@ protected function containHtml(string $htmlContent): string preg_match_all("/\/i", $htmlContent, $linksOutput); // Update relative links to be absolute, with instance url - if (isset($linksOutput[0]) && count($linksOutput[0]) > 0) { + if (count($linksOutput[0]) > 0) { foreach ($linksOutput[0] as $index => $linkMatch) { $oldLinkString = $linkMatch; $srcString = $linksOutput[2][$index]; diff --git a/app/Exports/ZipExports/ZipImportRunner.php b/app/Exports/ZipExports/ZipImportRunner.php index 382e4073eec..9fa7dec3afe 100644 --- a/app/Exports/ZipExports/ZipImportRunner.php +++ b/app/Exports/ZipExports/ZipImportRunner.php @@ -82,10 +82,8 @@ public function run(Import $import, ?Entity $parent = null): Entity $entity = $this->importBook($exportModel, $reader); } else if ($exportModel instanceof ZipExportChapter) { $entity = $this->importChapter($exportModel, $parent, $reader); - } else if ($exportModel instanceof ZipExportPage) { - $entity = $this->importPage($exportModel, $parent, $reader); } else { - throw new ZipImportException(['No importable data found in import data.']); + $entity = $this->importPage($exportModel, $parent, $reader); } $this->references->replaceReferences(); @@ -132,7 +130,7 @@ protected function importBook(ZipExportBook $exportBook, ZipExportReader $reader 'name' => $exportBook->name, 'description_html' => $exportBook->description_html ?? '', 'image' => $exportBook->cover ? $this->zipFileToUploadedFile($exportBook->cover, $reader) : null, - 'tags' => $this->exportTagsToInputArray($exportBook->tags ?? []), + 'tags' => $this->exportTagsToInputArray($exportBook->tags), ]); if ($book->coverInfo()->getImage()) { @@ -151,7 +149,7 @@ protected function importBook(ZipExportBook $exportBook, ZipExportReader $reader foreach ($children as $child) { if ($child instanceof ZipExportChapter) { $this->importChapter($child, $book, $reader); - } else if ($child instanceof ZipExportPage) { + } else { $this->importPage($child, $book, $reader); } } @@ -166,7 +164,7 @@ protected function importChapter(ZipExportChapter $exportChapter, Book $parent, $chapter = $this->chapterRepo->create([ 'name' => $exportChapter->name, 'description_html' => $exportChapter->description_html ?? '', - 'tags' => $this->exportTagsToInputArray($exportChapter->tags ?? []), + 'tags' => $this->exportTagsToInputArray($exportChapter->tags), ], $parent); $exportPages = $exportChapter->pages; @@ -199,7 +197,7 @@ protected function importPage(ZipExportPage $exportPage, Book|Chapter $parent, Z 'name' => $exportPage->name, 'markdown' => $exportPage->markdown ?? '', 'html' => $exportPage->html ?? '', - 'tags' => $this->exportTagsToInputArray($exportPage->tags ?? []), + 'tags' => $this->exportTagsToInputArray($exportPage->tags), ]); $this->references->addPage($page, $exportPage); @@ -302,7 +300,7 @@ protected function ensurePermissionsPermitImport(ZipExportPage|ZipExportChapter| array_push($chapters, ...$exportModel->chapters); } else if ($exportModel instanceof ZipExportChapter) { $chapters[] = $exportModel; - } else if ($exportModel instanceof ZipExportPage) { + } else { $pages[] = $exportModel; } diff --git a/app/Exports/ZipExports/ZipReferenceParser.php b/app/Exports/ZipExports/ZipReferenceParser.php index a6560e3f289..9bb069ab7f1 100644 --- a/app/Exports/ZipExports/ZipReferenceParser.php +++ b/app/Exports/ZipExports/ZipReferenceParser.php @@ -68,10 +68,6 @@ public function parseReferences(string $content, callable $handler): string $matches = []; preg_match_all($referenceRegex, $content, $matches); - if (count($matches) < 3) { - return $content; - } - for ($i = 0; $i < count($matches[0]); $i++) { $referenceText = $matches[0][$i]; $type = strtolower($matches[1][$i]); diff --git a/app/Http/Controller.php b/app/Http/Controller.php index 1a0f5932e6f..796505795e5 100644 --- a/app/Http/Controller.php +++ b/app/Http/Controller.php @@ -62,7 +62,7 @@ protected function showPermissionError(string $redirectLocation = '/'): never */ protected function checkPermission(string|Permission $permission): void { - if (!user() || !user()->can($permission)) { + if (!user()->can($permission)) { $this->showPermissionError(); } } diff --git a/app/Permissions/JointPermissionBuilder.php b/app/Permissions/JointPermissionBuilder.php index 56b22ad1604..94f18916d4a 100644 --- a/app/Permissions/JointPermissionBuilder.php +++ b/app/Permissions/JointPermissionBuilder.php @@ -61,8 +61,7 @@ public function rebuildForEntity(Entity $entity): void return; } - /** @var BookChild $entity */ - if ($entity->book) { + if ($entity instanceof BookChild) { $entities[] = $entity->book; } diff --git a/app/Search/SearchOptions.php b/app/Search/SearchOptions.php index 83af2d043d8..cfd068386ef 100644 --- a/app/Search/SearchOptions.php +++ b/app/Search/SearchOptions.php @@ -121,13 +121,11 @@ protected function addOptionsFromString(string $searchString): void foreach ($patterns as $termType => $pattern) { $matches = []; preg_match_all($pattern, $searchString, $matches); - if (count($matches) > 0) { - foreach ($matches[1] as $index => $value) { - $negated = str_starts_with($matches[0][$index], '-'); - $terms[$termType][] = $constructors[$termType]($value, $negated); - } - $searchString = preg_replace($pattern, '', $searchString); + foreach ($matches[1] as $index => $value) { + $negated = str_starts_with($matches[0][$index], '-'); + $terms[$termType][] = $constructors[$termType]($value, $negated); } + $searchString = preg_replace($pattern, '', $searchString); } // Unescape exacts and backslash escapes @@ -261,7 +259,7 @@ public function getAdditionalOptionsString(): string $userFilters = ['updated_by', 'created_by', 'owned_by']; $unsupportedFilters = ['is_template', 'sort_by']; foreach ($this->filters->all() as $filter) { - if (in_array($filter->getKey(), $userFilters, true) && $filter->value !== null && $filter->value !== 'me') { + if (in_array($filter->getKey(), $userFilters, true) && $filter->value && $filter->value !== 'me') { $options[] = $filter; } else if (in_array($filter->getKey(), $unsupportedFilters, true)) { $options[] = $filter; diff --git a/app/Sorting/BookSorter.php b/app/Sorting/BookSorter.php index b4f93d47b11..0862aaa8877 100644 --- a/app/Sorting/BookSorter.php +++ b/app/Sorting/BookSorter.php @@ -125,9 +125,8 @@ public function sortUsingMap(BookSortMap $sortMap): array */ protected function applySortUpdates(BookSortMapItem $sortMapItem, array $modelMap): void { - /** @var BookChild $model */ $model = $modelMap[$sortMapItem->type . ':' . $sortMapItem->id] ?? null; - if (!$model) { + if (!($model instanceof BookChild)) { return; } diff --git a/app/Uploads/ImageRepo.php b/app/Uploads/ImageRepo.php index a16b87bd75b..e87e22b3a3c 100644 --- a/app/Uploads/ImageRepo.php +++ b/app/Uploads/ImageRepo.php @@ -91,7 +91,7 @@ public function getEntityFiltered( $parentFilter = function (Builder $query) use ($filterType, $contextPage) { if ($filterType === 'page') { $query->where('uploaded_to', '=', $contextPage->id); - } else if ($filterType === 'book') { + } else { $validPageIds = $contextPage->book->pages() ->scopes('visible') ->pluck('id') diff --git a/app/Uploads/UserAvatars.php b/app/Uploads/UserAvatars.php index 0cc640f225c..8fcf6358008 100644 --- a/app/Uploads/UserAvatars.php +++ b/app/Uploads/UserAvatars.php @@ -148,7 +148,7 @@ protected function getAvatarImageData(string $url): string $responseCount++; $isRedirect = ($response->getStatusCode() === 301 || $response->getStatusCode() === 302); $url = $response->getHeader('Location')[0] ?? ''; - } while ($responseCount < 3 && $isRedirect && is_string($url) && str_starts_with($url, 'http')); + } while ($responseCount < 3 && $isRedirect && str_starts_with($url, 'http')); if ($responseCount === 3) { throw new HttpFetchException("Failed to fetch image, max redirect limit of 3 tries reached. Last fetched URL: {$url}"); diff --git a/app/Users/Models/User.php b/app/Users/Models/User.php index 50efdcdad60..b9289a5a7b5 100644 --- a/app/Users/Models/User.php +++ b/app/Users/Models/User.php @@ -222,8 +222,7 @@ public function hasSocialAccount(string $socialDriver = ''): bool public function getAvatar(int $size = 50): string { $default = url('/user_avatar.png'); - $imageId = $this->image_id; - if ($imageId === 0 || $imageId === '0' || $imageId === null) { + if ($this->image_id === 0) { return $default; } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 72189222fcf..bab28ea0eb3 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,11 +7,11 @@ parameters: - app # The level 8 is the highest level - level: 3 + level: 4 phpVersion: min: 80200 - max: 80400 + max: 80500 bootstrapFiles: - bootstrap/phpstan.php diff --git a/tests/Api/SearchApiTest.php b/tests/Api/SearchApiTest.php index 517c5d8e4ef..5d0ce53ec1c 100644 --- a/tests/Api/SearchApiTest.php +++ b/tests/Api/SearchApiTest.php @@ -106,6 +106,7 @@ public function test_all_endpoint_includes_parent_details_where_visible() $this->permissions->setEntityPermissions($page, ['view'], [$editor->roles()->first()]); $resp = $this->getJson($this->baseEndpoint . '?query=superextrauniquevalue'); + $resp->assertOk(); $resp->assertJsonPath('data.0.id', $page->id); $resp->assertJsonPath('data.0.book.name', $book->name); $resp->assertJsonMissingPath('data.0.chapter'); diff --git a/tests/Commands/CopyShelfPermissionsCommandTest.php b/tests/Commands/CopyShelfPermissionsCommandTest.php index 5c21a2e341c..d5f9677a229 100644 --- a/tests/Commands/CopyShelfPermissionsCommandTest.php +++ b/tests/Commands/CopyShelfPermissionsCommandTest.php @@ -2,6 +2,7 @@ namespace Tests\Commands; +use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use Tests\TestCase; @@ -61,4 +62,21 @@ public function test_copy_shelf_permissions_command_using_all() 'view' => true, 'update' => true, 'create' => false, 'delete' => false, ]); } + + public function test_copy_shelf_permissions_command_using_slug_without_interaction() + { + $shelf = $this->entities->shelfHasBooks(); + $editorRole = $this->users->editor()->roles()->first(); + /** @var Book $child */ + $child = $shelf->books()->first(); + $child->shelves()->where('id', '!=', $shelf->id)->delete(); + + $this->assertFalse($child->hasPermissions()); + + $this->permissions->setEntityPermissions($shelf, ['view', 'update'], [$editorRole]); + $this->artisan('bookstack:copy-shelf-permissions --all --no-interaction'); + + $child->refresh(); + $this->assertTrue($child->hasPermissions(), 'Child book should now be restricted'); + } } From 3d9d5fef51c29328aa071c906ccfda9dd77fed7b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 11 Apr 2026 14:34:54 +0100 Subject: [PATCH 102/204] Theme Modules: Updated install command to handle nested folder Theme module ZIPs will now support their files being in a single nested directory within a ZIP, to support common ZIP structure approaches. Added test to cover. For #6066 --- app/Theming/ThemeModuleZip.php | 55 ++++++++++++++++++++- dev/docs/theme-system-modules.md | 1 + tests/Commands/InstallModuleCommandTest.php | 29 +++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/app/Theming/ThemeModuleZip.php b/app/Theming/ThemeModuleZip.php index 7029fa0c6a0..4785abadbff 100644 --- a/app/Theming/ThemeModuleZip.php +++ b/app/Theming/ThemeModuleZip.php @@ -15,7 +15,41 @@ public function extractTo(string $destinationPath): void { $zip = new ZipArchive(); $zip->open($this->path); - $zip->extractTo($destinationPath); + $prefix = $this->getZipContentPrefix($zip); + + for ($i = 0; $i < $zip->numFiles; $i++) { + $name = $zip->getNameIndex($i); + $entryIsDir = str_ends_with($name, "/"); + if ($entryIsDir) { + continue; + } + + $stream = $zip->getStreamIndex($i); + + if ($prefix) { + if (!str_starts_with($name, $prefix) || $name === $prefix) { + continue; + } + $name = str_replace($prefix, '', $name); + } + + $targetPath = $destinationPath . DIRECTORY_SEPARATOR . $name; + $targetPathDir = dirname($targetPath); + if (!is_dir($targetPathDir)) { + $dirCreated = mkdir($targetPathDir, 0777, true); + if (!$dirCreated) { + throw new ThemeModuleException("Failed to create directory {$targetPathDir} when extracting module files"); + } + } + + $targetFile = fopen($targetPath, 'w'); + $written = stream_copy_to_stream($stream, $targetFile); + if (!$written) { + throw new ThemeModuleException("Failed to write to {$targetPath} when extracting module files"); + } + fclose($targetFile); + } + $zip->close(); } @@ -31,7 +65,8 @@ public function getModuleInstance(): ThemeModule throw new ThemeModuleException("Unable to open zip file at {$this->path}"); } - $moduleJsonText = $zip->getFromName('bookstack-module.json'); + $prefix = $this->getZipContentPrefix($zip); + $moduleJsonText = $zip->getFromName("{$prefix}bookstack-module.json"); $zip->close(); if ($moduleJsonText === false) { @@ -95,4 +130,20 @@ public function getContentsSize(): int return $totalSize; } + + protected function getZipContentPrefix(ZipArchive $zip): string + { + $index = $zip->locateName('bookstack-module.json', ZipArchive::FL_NODIR); + if ($index === false) { + return ''; + } + + $location = $zip->getNameIndex($index); + $pathParts = explode('/', $location); + if (count($pathParts) !== 2) { + return ''; + } + + return $pathParts[0] . '/'; + } } diff --git a/dev/docs/theme-system-modules.md b/dev/docs/theme-system-modules.md index 8aa9370ed26..0086ac9c013 100644 --- a/dev/docs/theme-system-modules.md +++ b/dev/docs/theme-system-modules.md @@ -66,6 +66,7 @@ Here are some general best practices when it comes to creating modules: ### Distribution Format Modules are expected to be distributed as a compressed ZIP file, where the ZIP contents follow that of a module folder. +Contents may optionally be placed within a nested folder inside the ZIP. BookStack provides a `php artisan bookstack:install-module` command which allows modules to be installed from these ZIP files, either from a local path or from a web URL. Currently, there's a hardcoded total filesize limit of 50MB for module contents installed via this method. diff --git a/tests/Commands/InstallModuleCommandTest.php b/tests/Commands/InstallModuleCommandTest.php index 8ffc4ead3a0..ee8da11283f 100644 --- a/tests/Commands/InstallModuleCommandTest.php +++ b/tests/Commands/InstallModuleCommandTest.php @@ -175,6 +175,35 @@ public function test_run_with_invalid_module_data_has_early_exit() ->assertExitCode(1); } + public function test_module_zip_when_files_in_nested_directory() + { + $this->usingThemeFolder(function ($themeFolder) { + $zip = new ZipArchive(); + $zipFile = tempnam(sys_get_temp_dir(), 'bs-test-module'); + $zip->open($zipFile, ZipArchive::CREATE); + + $zip->addEmptyDir('mod'); + $zip->addFromString('mod/bookstack-module.json', json_encode($metadata ?? [ + 'name' => 'Test Module', + 'description' => 'A test module for BookStack', + 'version' => '1.0.0', + ])); + $zip->addFromString('mod/functions.php', 'addEmptyDir('mod/a'); + $zip->addFromString('mod/a/cat.txt', 'Meow'); + $zip->close(); + + $this->artisan('bookstack:install-module', ['location' => $zipFile]) + ->expectsConfirmation('Are you sure you want to install this module?', 'yes') + ->assertExitCode(0); + + $modulePath = glob(theme_path('modules/*'), GLOB_ONLYDIR)[0]; + $this->assertFileExists($modulePath . '/a/cat.txt'); + $contents = file_get_contents($modulePath . '/a/cat.txt'); + $this->assertEquals('Meow', $contents); + }); + } + public function test_local_module_install_without_active_theme_can_setup_theme_folder() { $zip = $this->getModuleZipPath(); From 5fbaab474058c9c90496649d53dd002e25419884 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 11 Apr 2026 17:23:11 +0100 Subject: [PATCH 103/204] Theme modules: Allowed cross-origin redirects on download With a prompt to the user to confirm they trust the origin. For #6066 Added tests to cover. --- app/Console/Commands/InstallModuleCommand.php | 22 ++++++--- tests/Commands/InstallModuleCommandTest.php | 48 ++++++++++++++++++- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/app/Console/Commands/InstallModuleCommand.php b/app/Console/Commands/InstallModuleCommand.php index 20252525df8..114bfb105d8 100644 --- a/app/Console/Commands/InstallModuleCommand.php +++ b/app/Console/Commands/InstallModuleCommand.php @@ -213,15 +213,23 @@ protected function downloadModuleFile(string $location): string|null $redirectLocation = $resp->getHeaderLine('Location'); if ($redirectLocation) { $redirectUrl = parse_url($redirectLocation); - if ( - ($originalUrl['host'] ?? '') === ($redirectUrl['host'] ?? '') + $redirectOriginMatches = ($originalUrl['host'] ?? '') === ($redirectUrl['host'] ?? '') && ($originalUrl['scheme'] ?? '') === ($redirectUrl['scheme'] ?? '') - && ($originalUrl['port'] ?? '') === ($redirectUrl['port'] ?? '') - ) { - $currentLocation = $redirectLocation; - $redirectCount++; - continue; + && ($originalUrl['port'] ?? '') === ($redirectUrl['port'] ?? ''); + + if (!$redirectOriginMatches) { + $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?"); + if (!$shouldContinue) { + $this->error("Stopping module installation"); + return null; + } } + + $currentLocation = $redirectLocation; + $redirectCount++; + continue; } } diff --git a/tests/Commands/InstallModuleCommandTest.php b/tests/Commands/InstallModuleCommandTest.php index ee8da11283f..c085c49077d 100644 --- a/tests/Commands/InstallModuleCommandTest.php +++ b/tests/Commands/InstallModuleCommandTest.php @@ -96,18 +96,44 @@ public function test_remote_module_install_follows_redirects() }); } - public function test_remote_module_install_does_not_follow_redirects_to_different_origin() + public function test_remote_module_install_prompts_on_following_redirects_to_different_origin() { $this->usingThemeFolder(function () { $zip = $this->getModuleZipPath(); $http = $this->mockHttpClient([ new Response(302, ['Location' => 'http://example.com/a-test-module.zip']), + new Response(301, ['Location' => 'https://a.example.com:8080/a-test-module.zip']), new Response(200, ['Content-Length' => filesize($zip)], file_get_contents($zip)) ]); $this->artisan('bookstack:install-module', ['location' => 'https://example.com/test-module.zip']) ->expectsConfirmation('Are you sure you trust this source?', 'yes') + ->expectsOutput('The download URL is redirecting to a different site: http://example.com') + ->expectsConfirmation('Do you trust downloading the module from this site?', 'yes') + ->expectsOutput('The download URL is redirecting to a different site: https://a.example.com:8080') + ->expectsConfirmation('Do you trust downloading the module from this site?', 'yes') + ->assertExitCode(0); + + $this->assertEquals(3, $http->requestCount()); + $this->assertEquals('https', $http->requestAt(0)->getUri()->getScheme()); + $this->assertEquals('http', $http->requestAt(1)->getUri()->getScheme()); + $this->assertEquals('a.example.com', $http->requestAt(2)->getUri()->getHost()); + }); + } + + public function test_remote_module_install_redirect_origin_prompt_rejection() + { + $this->usingThemeFolder(function () { + $http = $this->mockHttpClient([ + new Response(302, ['Location' => 'http://example.com/a-test-module.zip']), + new Response(301, ['Location' => 'https://a.example.com:8080/a-test-module.zip']), + ]); + + $this->artisan('bookstack:install-module', ['location' => 'https://example.com/test-module.zip']) + ->expectsConfirmation('Are you sure you trust this source?', 'yes') + ->expectsOutput('The download URL is redirecting to a different site: http://example.com') + ->expectsConfirmation('Do you trust downloading the module from this site?', 'no') ->assertExitCode(1); $this->assertEquals(1, $http->requestCount()); @@ -115,6 +141,26 @@ public function test_remote_module_install_does_not_follow_redirects_to_differen }); } + public function test_remote_module_install_has_redirect_limit() + { + $this->usingThemeFolder(function () { + $http = $this->mockHttpClient([ + new Response(302, ['Location' => 'https://example.com/a-test-module.zip']), + new Response(302, ['Location' => 'https://example.com/b-test-module.zip']), + new Response(302, ['Location' => 'https://example.com/c-test-module.zip']), + new Response(302, ['Location' => 'https://example.com/d-test-module.zip']), + ]); + + $this->artisan('bookstack:install-module', ['location' => 'https://example.com/test-module.zip']) + ->expectsConfirmation('Are you sure you trust this source?', 'yes') + ->expectsOutput('ERROR: Failed to download module from https://example.com/test-module.zip') + ->assertExitCode(1); + + $this->assertEquals(4, $http->requestCount()); + $this->assertEquals('/c-test-module.zip', $http->requestAt(3)->getUri()->getPath()); + }); + } + public function test_remote_module_install_download_failures_are_announced_to_user() { $this->usingThemeFolder(function () { From 684a94c4195e7d0c7b10268cd4a99717ad637710 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 11 Apr 2026 18:49:34 +0100 Subject: [PATCH 104/204] Theme Modules: Prevented zip-slip in new module extraction method Updated the new (development only) approach which could result in zip-slip causing trouble. This adds path normalisation, and testing to cover. --- app/Theming/ThemeModuleManager.php | 9 ++++++++- app/Theming/ThemeModuleZip.php | 8 +++++++- tests/Commands/InstallModuleCommandTest.php | 17 +++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/app/Theming/ThemeModuleManager.php b/app/Theming/ThemeModuleManager.php index 86362a2f321..ed33014ebf4 100644 --- a/app/Theming/ThemeModuleManager.php +++ b/app/Theming/ThemeModuleManager.php @@ -51,7 +51,14 @@ public function addFromZip(string $name, ThemeModuleZip $zip): ThemeModule } $folderPath = $this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName; - $zip->extractTo($folderPath); + try { + $zip->extractTo($folderPath); + } catch (ThemeModuleException $exception) { + if (is_dir($folderPath)) { + $this->deleteDirectoryRecursively($folderPath); + } + throw new ThemeModuleException("Failed to load extract files from module ZIP with error: {$exception->getMessage()}"); + } $module = $this->loadFromFolder($folderName); if (!$module) { diff --git a/app/Theming/ThemeModuleZip.php b/app/Theming/ThemeModuleZip.php index 4785abadbff..7e94074c8e6 100644 --- a/app/Theming/ThemeModuleZip.php +++ b/app/Theming/ThemeModuleZip.php @@ -2,6 +2,7 @@ namespace BookStack\Theming; +use BookStack\Util\FilePathNormalizer; use ZipArchive; readonly class ThemeModuleZip @@ -33,7 +34,12 @@ public function extractTo(string $destinationPath): void $name = str_replace($prefix, '', $name); } - $targetPath = $destinationPath . DIRECTORY_SEPARATOR . $name; + try { + $targetPath = $destinationPath . DIRECTORY_SEPARATOR . FilePathNormalizer::normalize($name); + } catch (\Exception $exception) { + throw new ThemeModuleException("Bad file path found in module ZIP file: {$name}"); + } + $targetPathDir = dirname($targetPath); if (!is_dir($targetPathDir)) { $dirCreated = mkdir($targetPathDir, 0777, true); diff --git a/tests/Commands/InstallModuleCommandTest.php b/tests/Commands/InstallModuleCommandTest.php index c085c49077d..e96fc02c1a2 100644 --- a/tests/Commands/InstallModuleCommandTest.php +++ b/tests/Commands/InstallModuleCommandTest.php @@ -250,6 +250,23 @@ public function test_module_zip_when_files_in_nested_directory() }); } + public function test_module_install_negates_zip_slip() + { + $this->usingThemeFolder(function () { + $zip = $this->getModuleZipPath(null, [ + '../parent.txt' => str_repeat('dog', 10) + ]); + + $expectedInstallPath = theme_path('modules/test-module'); + $this->artisan('bookstack:install-module', ['location' => $zip]) + ->expectsConfirmation('Are you sure you want to install this module?', 'yes') + ->expectsOutput("ERROR: Failed to install module with error: Failed to load extract files from module ZIP with error: Bad file path found in module ZIP file: ../parent.txt") + ->assertExitCode(1); + + $this->assertDirectoryDoesNotExist($expectedInstallPath); + }); + } + public function test_local_module_install_without_active_theme_can_setup_theme_folder() { $zip = $this->getModuleZipPath(); From 4e3fa4822ff747d7a13c69c6d5d6a04899b33565 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 12 Apr 2026 14:31:40 +0100 Subject: [PATCH 105/204] Sort Rules: Added creation hints to sort rule selection To help direct/indicate how rules can be created. For #5967 --- lang/en/entities.php | 1 + resources/views/books/sort.blade.php | 12 +++++++++++- .../views/settings/categories/sorting.blade.php | 7 +++++-- tests/Sorting/BookSortTest.php | 15 +++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lang/en/entities.php b/lang/en/entities.php index 74c50be3b2f..5501d2bc229 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -173,6 +173,7 @@ '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', diff --git a/resources/views/books/sort.blade.php b/resources/views/books/sort.blade.php index e090708b1d7..37a87d9abfc 100644 --- a/resources/views/books/sort.blade.php +++ b/resources/views/books/sort.blade.php @@ -20,7 +20,12 @@

    {{ trans('entities.books_sort') }}

    -

    {{ trans('entities.books_sort_desc') }}

    +
    +

    {{ trans('entities.books_sort_desc') }}

    + @if(!userCan(\BookStack\Permissions\Permission::SettingsManage)) +

    {{ trans('entities.books_sort_auto_sort_creation_hint') }}

    + @endif +
    @php $autoSortVal = intval(old('auto-sort') ?? $book->sort_rule_id ?? 0); @@ -41,6 +46,11 @@ class="{{ $errors->has('auto-sort') ? 'neg' : '' }}"> @endforeach + @if(userCan(\BookStack\Permissions\Permission::SettingsManage)) +

    + {{ trans('settings.sort_rule_create') }} +

    + @endif
    diff --git a/resources/views/settings/categories/sorting.blade.php b/resources/views/settings/categories/sorting.blade.php index 0c9dc1f9595..5678434e0a5 100644 --- a/resources/views/settings/categories/sorting.blade.php +++ b/resources/views/settings/categories/sorting.blade.php @@ -38,9 +38,9 @@
    -

    {{ trans('settings.sorting_book_default_desc') }}

    +

    {{ trans('settings.sorting_book_default_desc') }}

    -
    +
    +

    + {{ trans('settings.sort_rule_create') }} +

    diff --git a/tests/Sorting/BookSortTest.php b/tests/Sorting/BookSortTest.php index 7f31f9c2739..33a10609a05 100644 --- a/tests/Sorting/BookSortTest.php +++ b/tests/Sorting/BookSortTest.php @@ -271,6 +271,21 @@ public function test_auto_sort_options_shown_on_sort_page() $this->withHtml($resp)->assertElementExists('select[name="auto-sort"] option[value="' . $sort->id . '"]'); } + public function test_auto_sort_rule_create_hint_shown_on_sort_page() + { + $book = $this->entities->book(); + $hintText = 'Auto sort option rules can be created in the "Lists & Sorting" settings area by a user with the relevant permissions.'; + + // Admin users see link for creating new rule + $resp = $this->asAdmin()->get($book->getUrl('/sort')); + $this->withHtml($resp)->assertLinkExists(url('/settings/sorting/rules/new'), 'Create Sort Rule'); + $resp->assertDontSee($hintText); + + // Non-admin users see help text + $resp = $this->asEditor()->get($book->getUrl('/sort')); + $resp->assertSee($hintText); + } + public function test_auto_sort_option_submit_saves_to_book() { $sort = SortRule::factory()->create(); From c7e2b487c14133ef3fa62321fb226501a0f3fc88 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 12 Apr 2026 15:17:31 +0100 Subject: [PATCH 106/204] Attachments: Aligned ZipExportAttachment link validation With controller routes. Don't consider this as a security issue, since the filtered URLs by that validation are very likely to be blocked by browser security or CSP, and there's a level of assumed privilege to the users that are able to create such attachments links already. Closes #6093 --- .../ZipExports/Models/ZipExportAttachment.php | 2 +- tests/Exports/ZipExportValidatorTest.php | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/Exports/ZipExports/Models/ZipExportAttachment.php b/app/Exports/ZipExports/Models/ZipExportAttachment.php index 97995738ffe..88c20e4d3d4 100644 --- a/app/Exports/ZipExports/Models/ZipExportAttachment.php +++ b/app/Exports/ZipExports/Models/ZipExportAttachment.php @@ -45,7 +45,7 @@ public static function validate(ZipValidationHelper $context, array $data): arra $rules = [ 'id' => ['nullable', 'int', $context->uniqueIdRule('attachment')], 'name' => ['required', 'string', 'min:1'], - 'link' => ['required_without:file', 'nullable', 'string'], + 'link' => ['required_without:file', 'nullable', 'string', 'safe_url'], 'file' => ['required_without:link', 'nullable', 'string', $context->fileReferenceRule()], ]; diff --git a/tests/Exports/ZipExportValidatorTest.php b/tests/Exports/ZipExportValidatorTest.php index c453ef294d4..e801705be1f 100644 --- a/tests/Exports/ZipExportValidatorTest.php +++ b/tests/Exports/ZipExportValidatorTest.php @@ -90,4 +90,29 @@ public function test_image_files_need_to_be_a_valid_detected_image_file() $this->assertEquals('The file needs to reference a file of type image/png,image/jpeg,image/gif,image/webp, found text/plain.', $results['page.images.0.file']); } + + public function test_page_link_attachments_cant_be_data_or_js() + { + $validateResultCountByLink = [ + 'data:text/html,

    hi

    ' => 1, + 'javascript:alert(\'hi\')' => 1, + 'mailto:email@example.com' => 0, + ]; + + foreach ($validateResultCountByLink as $link => $count) { + $validator = $this->getValidatorForData([ + 'page' => [ + 'id' => 4, + 'name' => 'My page', + 'markdown' => 'hello', + 'attachments' => [ + ['id' => 4, 'name' => 'Attachment A', 'link' => $link], + ], + ] + ]); + + $results = $validator->validate(); + $this->assertCount($count, $results); + } + } } From 4feb50e7ee06206b2ef037090673cabb70a17c73 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 12 Apr 2026 15:29:00 +0100 Subject: [PATCH 107/204] Attachments: Aligned attachment validation a little more --- app/Exports/ZipExports/Models/ZipExportAttachment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Exports/ZipExports/Models/ZipExportAttachment.php b/app/Exports/ZipExports/Models/ZipExportAttachment.php index 88c20e4d3d4..1611866875b 100644 --- a/app/Exports/ZipExports/Models/ZipExportAttachment.php +++ b/app/Exports/ZipExports/Models/ZipExportAttachment.php @@ -45,7 +45,7 @@ public static function validate(ZipValidationHelper $context, array $data): arra $rules = [ 'id' => ['nullable', 'int', $context->uniqueIdRule('attachment')], 'name' => ['required', 'string', 'min:1'], - 'link' => ['required_without:file', 'nullable', 'string', 'safe_url'], + 'link' => ['required_without:file', 'nullable', 'string', 'max:2000', 'safe_url'], 'file' => ['required_without:link', 'nullable', 'string', $context->fileReferenceRule()], ]; From f14fc68b6697c203f4012c5bf537dcb49db85f87 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 12 Apr 2026 18:26:00 +0100 Subject: [PATCH 108/204] API: Added new tags API endpoints --- app/Activity/Controllers/TagApiController.php | 51 +++++++++++++++++++ app/Activity/Controllers/TagController.php | 4 +- app/Activity/TagRepo.php | 34 ++++++++++--- app/Api/ListingResponseBuilder.php | 32 +++++++++--- app/Http/ApiController.php | 6 ++- routes/api.php | 4 ++ 6 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 app/Activity/Controllers/TagApiController.php diff --git a/app/Activity/Controllers/TagApiController.php b/app/Activity/Controllers/TagApiController.php new file mode 100644 index 00000000000..c1945ba5c62 --- /dev/null +++ b/app/Activity/Controllers/TagApiController.php @@ -0,0 +1,51 @@ +tagRepo + ->queryWithTotalsForApi(''); + + return $this->apiListingResponse($tagQuery, [ + 'name', 'values', 'usages', 'page_count', 'chapter_count', 'book_count', 'shelf_count', + ], [], [ + 'name' + ]); + } + + /** + * Get a list of tag values used in the system, which have been used for the given tag name. + * You'll only see results based on tags applied to content you have access to. + * Only the value field can be used in filters. + */ + public function listValues(string $name): JsonResponse + { + $tagQuery = $this->tagRepo + ->queryWithTotalsForApi($name); + + return $this->apiListingResponse($tagQuery, [ + 'name', 'value', 'usages', 'page_count', 'chapter_count', 'book_count', 'shelf_count', + ], [], [ + 'name', 'value', + ]); + } +} diff --git a/app/Activity/Controllers/TagController.php b/app/Activity/Controllers/TagController.php index 0af8835ca77..723dc4ab474 100644 --- a/app/Activity/Controllers/TagController.php +++ b/app/Activity/Controllers/TagController.php @@ -24,9 +24,9 @@ public function index(Request $request) 'usages' => trans('entities.tags_usages'), ]); - $nameFilter = $request->get('name', ''); + $nameFilter = $request->input('name', ''); $tags = $this->tagRepo - ->queryWithTotals($listOptions, $nameFilter) + ->queryWithTotalsForList($listOptions, $nameFilter) ->paginate(50) ->appends(array_filter(array_merge($listOptions->getPaginationAppends(), [ 'name' => $nameFilter, diff --git a/app/Activity/TagRepo.php b/app/Activity/TagRepo.php index 82c26b00e28..3e8d5545ab6 100644 --- a/app/Activity/TagRepo.php +++ b/app/Activity/TagRepo.php @@ -18,9 +18,10 @@ public function __construct( } /** - * Start a query against all tags in the system. + * Start a query against all tags in the system, with total counts for their usage, + * suitable for a system interface list with listing options. */ - public function queryWithTotals(SimpleListOptions $listOptions, string $nameFilter): Builder + public function queryWithTotalsForList(SimpleListOptions $listOptions, string $nameFilter): Builder { $searchTerm = $listOptions->getSearch(); $sort = $listOptions->getSort(); @@ -28,17 +29,34 @@ public function queryWithTotals(SimpleListOptions $listOptions, string $nameFilt $sort = 'value'; } + $query = $this->baseQueryWithTotals($nameFilter, $searchTerm) + ->orderBy($sort, $listOptions->getOrder()); + + return $this->permissions->restrictEntityRelationQuery($query, 'tags', 'entity_id', 'entity_type'); + } + + /** + * Start a query against all tags in the system, with total counts for their usage, + * which can be used via the API. + */ + public function queryWithTotalsForApi(string $nameFilter): Builder + { + $query = $this->baseQueryWithTotals($nameFilter, ''); + return $this->permissions->restrictEntityRelationQuery($query, 'tags', 'entity_id', 'entity_type'); + } + + protected function baseQueryWithTotals(string $nameFilter, string $searchTerm): Builder + { $query = Tag::query() ->select([ 'name', ($searchTerm || $nameFilter) ? 'value' : DB::raw('COUNT(distinct value) as `values`'), DB::raw('COUNT(id) as usages'), - DB::raw('SUM(IF(entity_type = \'page\', 1, 0)) as page_count'), - DB::raw('SUM(IF(entity_type = \'chapter\', 1, 0)) as chapter_count'), - DB::raw('SUM(IF(entity_type = \'book\', 1, 0)) as book_count'), - DB::raw('SUM(IF(entity_type = \'bookshelf\', 1, 0)) as shelf_count'), + DB::raw('CAST(SUM(IF(entity_type = \'page\', 1, 0)) as UNSIGNED) as page_count'), + DB::raw('CAST(SUM(IF(entity_type = \'chapter\', 1, 0)) as UNSIGNED) as chapter_count'), + DB::raw('CAST(SUM(IF(entity_type = \'book\', 1, 0)) as UNSIGNED) as book_count'), + DB::raw('CAST(SUM(IF(entity_type = \'bookshelf\', 1, 0)) as UNSIGNED) as shelf_count'), ]) - ->orderBy($sort, $listOptions->getOrder()) ->whereHas('entity'); if ($nameFilter) { @@ -57,7 +75,7 @@ public function queryWithTotals(SimpleListOptions $listOptions, string $nameFilt }); } - return $this->permissions->restrictEntityRelationQuery($query, 'tags', 'entity_id', 'entity_type'); + return $query; } /** diff --git a/app/Api/ListingResponseBuilder.php b/app/Api/ListingResponseBuilder.php index 44117bad975..6b9cfdd7d0d 100644 --- a/app/Api/ListingResponseBuilder.php +++ b/app/Api/ListingResponseBuilder.php @@ -18,6 +18,13 @@ class ListingResponseBuilder */ protected array $fields; + /** + * Which fields are filterable. + * When null, the $fields above are used instead (Allow all fields). + * @var string[]|null + */ + protected array|null $filterableFields = null; + /** * @var array */ @@ -54,7 +61,7 @@ public function toResponse(): JsonResponse { $filteredQuery = $this->filterQuery($this->query); - $total = $filteredQuery->count(); + $total = $filteredQuery->getCountForPagination(); $data = $this->fetchData($filteredQuery)->each(function ($model) { foreach ($this->resultModifiers as $modifier) { $modifier($model); @@ -77,6 +84,14 @@ public function modifyResults(callable $modifier): void $this->resultModifiers[] = $modifier; } + /** + * Limit filtering to just the given set of fields. + */ + public function setFilterableFields(array $fields): void + { + $this->filterableFields = $fields; + } + /** * Fetch the data to return within the response. */ @@ -94,7 +109,7 @@ protected function fetchData(Builder $query): Collection protected function filterQuery(Builder $query): Builder { $query = clone $query; - $requestFilters = $this->request->get('filter', []); + $requestFilters = $this->request->input('filter', []); if (!is_array($requestFilters)) { return $query; } @@ -114,10 +129,11 @@ protected function filterQuery(Builder $query): Builder protected function requestFilterToQueryFilter($fieldKey, $value): ?array { $splitKey = explode(':', $fieldKey); - $field = $splitKey[0]; + $field = strtolower($splitKey[0]); $filterOperator = $splitKey[1] ?? 'eq'; - if (!in_array($field, $this->fields)) { + $filterFields = $this->filterableFields ?? $this->fields; + if (!in_array($field, $filterFields)) { return null; } @@ -140,8 +156,8 @@ protected function sortQuery(Builder $query): Builder $defaultSortName = $this->fields[0]; $direction = 'asc'; - $sort = $this->request->get('sort', ''); - if (strpos($sort, '-') === 0) { + $sort = $this->request->input('sort', ''); + if (str_starts_with($sort, '-')) { $direction = 'desc'; } @@ -160,9 +176,9 @@ protected function sortQuery(Builder $query): Builder protected function countAndOffsetQuery(Builder $query): Builder { $query = clone $query; - $offset = max(0, $this->request->get('offset', 0)); + $offset = max(0, $this->request->input('offset', 0)); $maxCount = config('api.max_item_count'); - $count = $this->request->get('count', config('api.default_item_count')); + $count = $this->request->input('count', config('api.default_item_count')); $count = max(min($maxCount, $count), 1); return $query->skip($offset)->take($count); diff --git a/app/Http/ApiController.php b/app/Http/ApiController.php index 8c0f206d0d5..f1b74783f8a 100644 --- a/app/Http/ApiController.php +++ b/app/Http/ApiController.php @@ -20,10 +20,14 @@ abstract class ApiController extends Controller * Provide a paginated listing JSON response in a standard format * taking into account any pagination parameters passed by the user. */ - protected function apiListingResponse(Builder $query, array $fields, array $modifiers = []): JsonResponse + protected function apiListingResponse(Builder $query, array $fields, array $modifiers = [], array $filterableFields = []): JsonResponse { $listing = new ListingResponseBuilder($query, request(), $fields); + if (count($filterableFields) > 0) { + $listing->setFilterableFields($filterableFields); + } + foreach ($modifiers as $modifier) { $listing->modifyResults($modifier); } diff --git a/routes/api.php b/routes/api.php index 308a95d8c28..9f45cefb985 100644 --- a/routes/api.php +++ b/routes/api.php @@ -7,6 +7,7 @@ */ use BookStack\Activity\Controllers as ActivityControllers; +use BookStack\Activity\Controllers\TagApiController; use BookStack\Api\ApiDocsController; use BookStack\App\SystemApiController; use BookStack\Entities\Controllers as EntityControllers; @@ -109,6 +110,9 @@ Route::get('system', [SystemApiController::class, 'read']); +Route::get('tags/names', [TagApiController::class, 'listNames']); +Route::get('tags/name/{name}/values', [TagApiController::class, 'listValues']); + Route::get('users', [UserApiController::class, 'list']); Route::post('users', [UserApiController::class, 'create']); Route::get('users/{id}', [UserApiController::class, 'read']); From 1c1ad1d1b739d2558d31478610b1ca4af553f3eb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 12 Apr 2026 20:45:18 +0100 Subject: [PATCH 109/204] Tags API: Reviewed docs and added examples --- app/Activity/Controllers/TagApiController.php | 10 ++++-- app/Api/ApiDocsGenerator.php | 7 ++-- dev/api/responses/tags-list-names.json | 32 +++++++++++++++++++ dev/api/responses/tags-list-values.json | 32 +++++++++++++++++++ .../views/api-docs/parts/endpoint.blade.php | 2 +- .../api-docs/parts/getting-started.blade.php | 2 +- 6 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 dev/api/responses/tags-list-names.json create mode 100644 dev/api/responses/tags-list-values.json diff --git a/app/Activity/Controllers/TagApiController.php b/app/Activity/Controllers/TagApiController.php index c1945ba5c62..1fdffa007a5 100644 --- a/app/Activity/Controllers/TagApiController.php +++ b/app/Activity/Controllers/TagApiController.php @@ -8,6 +8,12 @@ use BookStack\Http\ApiController; use Illuminate\Http\JsonResponse; +/** + * Endpoints to query data about tags in the system. + * You'll only see results based on tags applied to content you have access to. + * There are no general create/update/delete endpoints here since tags do not exist + * by themselves, they are managed via the items they are assigned to. + */ class TagApiController extends ApiController { public function __construct( @@ -17,7 +23,6 @@ public function __construct( /** * Get a list of tag names used in the system. - * You'll only see results based on tags applied to content you have access to. * Only the name field can be used in filters. */ public function listNames(): JsonResponse @@ -33,8 +38,7 @@ public function listNames(): JsonResponse } /** - * Get a list of tag values used in the system, which have been used for the given tag name. - * You'll only see results based on tags applied to content you have access to. + * Get a list of tag values, which have been set for the given tag name. * Only the value field can be used in filters. */ public function listValues(string $name): JsonResponse diff --git a/app/Api/ApiDocsGenerator.php b/app/Api/ApiDocsGenerator.php index a59cb8198e2..53cb2890a7e 100644 --- a/app/Api/ApiDocsGenerator.php +++ b/app/Api/ApiDocsGenerator.php @@ -195,11 +195,12 @@ protected function getReflectionClass(string $className): ReflectionClass protected function getFlatApiRoutes(): Collection { return collect(Route::getRoutes()->getRoutes())->filter(function ($route) { - return strpos($route->uri, 'api/') === 0; + return str_starts_with($route->uri, 'api/'); })->map(function ($route) { [$controller, $controllerMethod] = explode('@', $route->action['uses']); $baseModelName = explode('.', explode('/', $route->uri)[1])[0]; - $shortName = $baseModelName . '-' . $controllerMethod; + $controllerMethodKebab = Str::kebab($controllerMethod); + $shortName = $baseModelName . '-' . $controllerMethodKebab; return [ 'name' => $shortName, @@ -207,7 +208,7 @@ protected function getFlatApiRoutes(): Collection 'method' => $route->methods[0], 'controller' => $controller, 'controller_method' => $controllerMethod, - 'controller_method_kebab' => Str::kebab($controllerMethod), + 'controller_method_kebab' => $controllerMethodKebab, 'base_model' => $baseModelName, ]; }); diff --git a/dev/api/responses/tags-list-names.json b/dev/api/responses/tags-list-names.json new file mode 100644 index 00000000000..c0c8e7b2231 --- /dev/null +++ b/dev/api/responses/tags-list-names.json @@ -0,0 +1,32 @@ +{ + "data": [ + { + "name": "Category", + "values": 8, + "usages": 184, + "page_count": 3, + "chapter_count": 8, + "book_count": 171, + "shelf_count": 2 + }, + { + "name": "Review Due", + "values": 2, + "usages": 2, + "page_count": 1, + "chapter_count": 0, + "book_count": 1, + "shelf_count": 0 + }, + { + "name": "Type", + "values": 2, + "usages": 2, + "page_count": 0, + "chapter_count": 1, + "book_count": 1, + "shelf_count": 0 + } + ], + "total": 3 +} \ No newline at end of file diff --git a/dev/api/responses/tags-list-values.json b/dev/api/responses/tags-list-values.json new file mode 100644 index 00000000000..37926b8463c --- /dev/null +++ b/dev/api/responses/tags-list-values.json @@ -0,0 +1,32 @@ +{ + "data": [ + { + "name": "Category", + "value": "Cool Stuff", + "usages": 3, + "page_count": 1, + "chapter_count": 0, + "book_count": 2, + "shelf_count": 0 + }, + { + "name": "Category", + "value": "Top Content", + "usages": 168, + "page_count": 0, + "chapter_count": 3, + "book_count": 165, + "shelf_count": 0 + }, + { + "name": "Category", + "value": "Learning", + "usages": 2, + "page_count": 0, + "chapter_count": 0, + "book_count": 0, + "shelf_count": 2 + } + ], + "total": 3 +} \ No newline at end of file diff --git a/resources/views/api-docs/parts/endpoint.blade.php b/resources/views/api-docs/parts/endpoint.blade.php index 024a5ecdf04..543ef092ee5 100644 --- a/resources/views/api-docs/parts/endpoint.blade.php +++ b/resources/views/api-docs/parts/endpoint.blade.php @@ -1,7 +1,7 @@
    {{ $endpoint['method'] }}
    - @if($endpoint['controller_method_kebab'] === 'list') + @if(str_starts_with($endpoint['controller_method_kebab'], 'list') && !str_contains($endpoint['uri'], '{')) {{ url($endpoint['uri']) }} @else {{ url($endpoint['uri']) }} diff --git a/resources/views/api-docs/parts/getting-started.blade.php b/resources/views/api-docs/parts/getting-started.blade.php index 663389047ce..ebe3838ef1f 100644 --- a/resources/views/api-docs/parts/getting-started.blade.php +++ b/resources/views/api-docs/parts/getting-started.blade.php @@ -2,7 +2,7 @@

    This documentation covers use of the REST API.
    - Examples of API usage, in a variety of programming languages, can be found in the BookStack api-scripts repo on GitHub. + Examples of API usage, in a variety of programming languages, can be found in the BookStack api-scripts repo on Codeberg.

    Some alternative options for extension and customization can be found below: From 346dc27979a05a7b82e7fef5f32c00be2578f77f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 14 Apr 2026 11:31:34 +0100 Subject: [PATCH 110/204] API: Added testing to cover tags API endpoints --- tests/Api/TagsApiTest.php | 109 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 tests/Api/TagsApiTest.php diff --git a/tests/Api/TagsApiTest.php b/tests/Api/TagsApiTest.php new file mode 100644 index 00000000000..baf7b085956 --- /dev/null +++ b/tests/Api/TagsApiTest.php @@ -0,0 +1,109 @@ + 'MyGreatApiTag', 'value' => 'cat']; + $pagesToTag = Page::query()->take(10)->get(); + $booksToTag = Book::query()->take(3)->get(); + $chaptersToTag = Chapter::query()->take(5)->get(); + $pagesToTag->each(fn (Page $page) => $page->tags()->save(new Tag($tagInfo))); + $booksToTag->each(fn (Book $book) => $book->tags()->save(new Tag($tagInfo))); + $chaptersToTag->each(fn (Chapter $chapter) => $chapter->tags()->save(new Tag($tagInfo))); + + $resp = $this->actingAsApiEditor()->getJson('api/tags/names?filter[name]=MyGreatApiTag'); + $resp->assertStatus(200); + $resp->assertJson([ + 'data' => [ + [ + 'name' => 'MyGreatApiTag', + 'values' => 1, + 'usages' => 18, + 'page_count' => 10, + 'book_count' => 3, + 'chapter_count' => 5, + 'shelf_count' => 0, + ] + ], + 'total' => 1, + ]); + } + + public function test_list_names_is_limited_by_permission_visibility(): void + { + $pagesToTag = Page::query()->take(10)->get(); + $pagesToTag->each(fn (Page $page) => $page->tags()->save(new Tag(['name' => 'MyGreatApiTag', 'value' => 'cat' . $page->id]))); + + $this->permissions->disableEntityInheritedPermissions($pagesToTag[3]); + $this->permissions->disableEntityInheritedPermissions($pagesToTag[6]); + + $resp = $this->actingAsApiEditor()->getJson('api/tags/names?filter[name]=MyGreatApiTag'); + $resp->assertStatus(200); + $resp->assertJson([ + 'data' => [ + [ + 'name' => 'MyGreatApiTag', + 'values' => 8, + 'usages' => 8, + 'page_count' => 8, + 'book_count' => 0, + 'chapter_count' => 0, + 'shelf_count' => 0, + ] + ], + 'total' => 1, + ]); + } + + public function test_list_values_returns_values_for_set_tag() + { + $pagesToTag = Page::query()->take(10)->get(); + $booksToTag = Book::query()->take(3)->get(); + $chaptersToTag = Chapter::query()->take(5)->get(); + $pagesToTag->each(fn (Page $page) => $page->tags()->save(new Tag(['name' => 'MyValueApiTag', 'value' => 'tag-page' . $page->id]))); + $booksToTag->each(fn (Book $book) => $book->tags()->save(new Tag(['name' => 'MyValueApiTag', 'value' => 'tag-book' . $book->id]))); + $chaptersToTag->each(fn (Chapter $chapter) => $chapter->tags()->save(new Tag(['name' => 'MyValueApiTag', 'value' => 'tag-chapter' . $chapter->id]))); + + $resp = $this->actingAsApiEditor()->getJson('api/tags/name/MyValueApiTag/values'); + + $resp->assertStatus(200); + $resp->assertJson(['total' => 18]); + $resp->assertJsonFragment([ + [ + 'name' => 'MyValueApiTag', + 'value' => 'tag-page' . $pagesToTag[0]->id, + 'usages' => 1, + 'page_count' => 1, + 'book_count' => 0, + 'chapter_count' => 0, + 'shelf_count' => 0, + ] + ]); + } + + public function test_list_values_is_limited_by_permission_visibility(): void + { + $pagesToTag = Page::query()->take(10)->get(); + $pagesToTag->each(fn (Page $page) => $page->tags()->save(new Tag(['name' => 'MyGreatApiTag', 'value' => 'cat' . $page->id]))); + + $this->permissions->disableEntityInheritedPermissions($pagesToTag[3]); + $this->permissions->disableEntityInheritedPermissions($pagesToTag[6]); + + $resp = $this->actingAsApiEditor()->getJson('api/tags/name/MyGreatApiTag/values'); + $resp->assertStatus(200); + $resp->assertJson(['total' => 8]); + $resp->assertJsonMissing(['value' => 'cat' . $pagesToTag[3]->id]); + } +} From 208629ee1fce280c31f9baf30a6ed79c3cd17df0 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 14 Apr 2026 12:03:29 +0100 Subject: [PATCH 111/204] API: Some changes to tag API endpoints - Updated tag values endpoint to use query param instead of path argument, so a better range of values can be provided (including those with slashes). - Updated image gallery example request to align with docs use changes. --- app/Activity/Controllers/TagApiController.php | 23 +++++++++++++++---- ...p => image-gallery-read-data-for-url.http} | 0 dev/api/requests/tags-list-values.http | 1 + routes/api.php | 2 +- tests/Api/TagsApiTest.php | 4 ++-- 5 files changed, 22 insertions(+), 8 deletions(-) rename dev/api/requests/{image-gallery-readDataForUrl.http => image-gallery-read-data-for-url.http} (100%) create mode 100644 dev/api/requests/tags-list-values.http diff --git a/app/Activity/Controllers/TagApiController.php b/app/Activity/Controllers/TagApiController.php index 1fdffa007a5..f5c5e95d420 100644 --- a/app/Activity/Controllers/TagApiController.php +++ b/app/Activity/Controllers/TagApiController.php @@ -7,6 +7,7 @@ use BookStack\Activity\TagRepo; use BookStack\Http\ApiController; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; /** * Endpoints to query data about tags in the system. @@ -21,6 +22,15 @@ public function __construct( ) { } + protected function rules(): array + { + return [ + 'listValues' => [ + 'name' => ['required', 'string'], + ], + ]; + } + /** * Get a list of tag names used in the system. * Only the name field can be used in filters. @@ -38,18 +48,21 @@ public function listNames(): JsonResponse } /** - * Get a list of tag values, which have been set for the given tag name. + * Get a list of tag values, which have been set for the given tag name, + * which must be provided as a query parameter on the request. * Only the value field can be used in filters. */ - public function listValues(string $name): JsonResponse + public function listValues(Request $request): JsonResponse { - $tagQuery = $this->tagRepo - ->queryWithTotalsForApi($name); + $data = $this->validate($request, $this->rules()['listValues']); + $name = $data['name']; + + $tagQuery = $this->tagRepo->queryWithTotalsForApi($name); return $this->apiListingResponse($tagQuery, [ 'name', 'value', 'usages', 'page_count', 'chapter_count', 'book_count', 'shelf_count', ], [], [ - 'name', 'value', + 'value', ]); } } diff --git a/dev/api/requests/image-gallery-readDataForUrl.http b/dev/api/requests/image-gallery-read-data-for-url.http similarity index 100% rename from dev/api/requests/image-gallery-readDataForUrl.http rename to dev/api/requests/image-gallery-read-data-for-url.http diff --git a/dev/api/requests/tags-list-values.http b/dev/api/requests/tags-list-values.http new file mode 100644 index 00000000000..6dd3f49fc0b --- /dev/null +++ b/dev/api/requests/tags-list-values.http @@ -0,0 +1 @@ +GET /api/tags/values-for-name?name=Category diff --git a/routes/api.php b/routes/api.php index 9f45cefb985..5a9df3cc422 100644 --- a/routes/api.php +++ b/routes/api.php @@ -111,7 +111,7 @@ Route::get('system', [SystemApiController::class, 'read']); Route::get('tags/names', [TagApiController::class, 'listNames']); -Route::get('tags/name/{name}/values', [TagApiController::class, 'listValues']); +Route::get('tags/values-for-name', [TagApiController::class, 'listValues']); Route::get('users', [UserApiController::class, 'list']); Route::post('users', [UserApiController::class, 'create']); diff --git a/tests/Api/TagsApiTest.php b/tests/Api/TagsApiTest.php index baf7b085956..a079fa63915 100644 --- a/tests/Api/TagsApiTest.php +++ b/tests/Api/TagsApiTest.php @@ -76,7 +76,7 @@ public function test_list_values_returns_values_for_set_tag() $booksToTag->each(fn (Book $book) => $book->tags()->save(new Tag(['name' => 'MyValueApiTag', 'value' => 'tag-book' . $book->id]))); $chaptersToTag->each(fn (Chapter $chapter) => $chapter->tags()->save(new Tag(['name' => 'MyValueApiTag', 'value' => 'tag-chapter' . $chapter->id]))); - $resp = $this->actingAsApiEditor()->getJson('api/tags/name/MyValueApiTag/values'); + $resp = $this->actingAsApiEditor()->getJson('api/tags/values-for-name?name=MyValueApiTag'); $resp->assertStatus(200); $resp->assertJson(['total' => 18]); @@ -101,7 +101,7 @@ public function test_list_values_is_limited_by_permission_visibility(): void $this->permissions->disableEntityInheritedPermissions($pagesToTag[3]); $this->permissions->disableEntityInheritedPermissions($pagesToTag[6]); - $resp = $this->actingAsApiEditor()->getJson('api/tags/name/MyGreatApiTag/values'); + $resp = $this->actingAsApiEditor()->getJson('api/tags/values-for-name?name=MyGreatApiTag'); $resp->assertStatus(200); $resp->assertJson(['total' => 8]); $resp->assertJsonMissing(['value' => 'cat' . $pagesToTag[3]->id]); From 18364d1e6e235ab129a03e77279201436bcd954a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 16 Apr 2026 11:11:06 +0100 Subject: [PATCH 112/204] WYSIWYG: Added inline code support to minimal editor Used for comments and descriptions. Also updated shortcut handling that we're not registering shortcuts for edits which can't use the related formatting types. For #6003 --- app/Util/HtmlDescriptionFilter.php | 1 + resources/js/wysiwyg/index.ts | 6 +- resources/js/wysiwyg/services/shortcuts.ts | 59 +++++++++++--------- resources/js/wysiwyg/ui/defaults/toolbars.ts | 1 + tests/Entity/BookTest.php | 4 +- 5 files changed, 40 insertions(+), 31 deletions(-) diff --git a/app/Util/HtmlDescriptionFilter.php b/app/Util/HtmlDescriptionFilter.php index 1baa11ffcfa..ba145460381 100644 --- a/app/Util/HtmlDescriptionFilter.php +++ b/app/Util/HtmlDescriptionFilter.php @@ -27,6 +27,7 @@ class HtmlDescriptionFilter 'span' => [], 'em' => [], 'br' => [], + 'code' => [], ]; public static function filterFromString(string $html): string diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index 01964b066c6..dc0ea211f59 100644 --- a/resources/js/wysiwyg/index.ts +++ b/resources/js/wysiwyg/index.ts @@ -59,7 +59,7 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st mergeRegister( registerRichText(editor), registerHistory(editor, createEmptyHistoryState(), 300), - registerShortcuts(context), + registerShortcuts(context, true), registerKeyboardHandling(context), registerMouseHandling(context), registerSelectionHandling(context), @@ -123,7 +123,7 @@ export function createBasicEditorInstance(container: HTMLElement, htmlContent: s const editorTeardown = mergeRegister( registerRichText(editor), registerHistory(editor, createEmptyHistoryState(), 300), - registerShortcuts(context), + registerShortcuts(context, false), registerAutoLinks(editor), ); @@ -157,7 +157,7 @@ export function createCommentEditorInstance(container: HTMLElement, htmlContent: const editorTeardown = mergeRegister( registerRichText(editor), registerHistory(editor, createEmptyHistoryState(), 300), - registerShortcuts(context), + registerShortcuts(context, false), registerAutoLinks(editor), registerMentions(context), ); diff --git a/resources/js/wysiwyg/services/shortcuts.ts b/resources/js/wysiwyg/services/shortcuts.ts index c4be0f3cf2f..00abe0c6d2f 100644 --- a/resources/js/wysiwyg/services/shortcuts.ts +++ b/resources/js/wysiwyg/services/shortcuts.ts @@ -38,29 +38,9 @@ type ShortcutAction = (editor: LexicalEditor, context: EditorUiContext) => boole * List of action functions by their shortcut combo. * We use "meta" as an abstraction for ctrl/cmd depending on platform. */ -const actionsByKeys: Record = { - 'meta+s': () => { - window.$events.emit('editor-save-draft'); - return true; - }, - 'meta+enter': () => { - window.$events.emit('editor-save-page'); - return true; - }, - 'meta+1': (editor, context) => headerHandler(context, 'h2'), - 'meta+2': (editor, context) => headerHandler(context, 'h3'), - 'meta+3': (editor, context) => headerHandler(context, 'h4'), - 'meta+4': (editor, context) => headerHandler(context, 'h5'), - 'meta+5': wrapFormatAction(toggleSelectionAsParagraph), - 'meta+d': wrapFormatAction(toggleSelectionAsParagraph), - 'meta+6': wrapFormatAction(toggleSelectionAsBlockquote), - 'meta+q': wrapFormatAction(toggleSelectionAsBlockquote), - 'meta+7': wrapFormatAction(formatCodeBlock), - 'meta+e': wrapFormatAction(formatCodeBlock), +const baseActionsByKeys: Record = { 'meta+8': toggleInlineCode, 'meta+shift+e': toggleInlineCode, - 'meta+9': wrapFormatAction(cycleSelectionCalloutFormats), - 'meta+o': wrapFormatAction((e) => toggleSelectionAsList(e, 'number')), 'meta+p': wrapFormatAction((e) => toggleSelectionAsList(e, 'bullet')), 'meta+k': (editor, context) => { @@ -87,12 +67,39 @@ const actionsByKeys: Record = { }, }; -function createKeyDownListener(context: EditorUiContext): (e: KeyboardEvent) => void { +/** + * An extended set of the above, used for fuller-featured editors with heavier block-level formatting. + */ +const extendedActionsByKeys: Record = { + ...baseActionsByKeys, + 'meta+s': () => { + window.$events.emit('editor-save-draft'); + return true; + }, + 'meta+enter': () => { + window.$events.emit('editor-save-page'); + return true; + }, + 'meta+1': (editor, context) => headerHandler(context, 'h2'), + 'meta+2': (editor, context) => headerHandler(context, 'h3'), + 'meta+3': (editor, context) => headerHandler(context, 'h4'), + 'meta+4': (editor, context) => headerHandler(context, 'h5'), + 'meta+5': wrapFormatAction(toggleSelectionAsParagraph), + 'meta+d': wrapFormatAction(toggleSelectionAsParagraph), + 'meta+6': wrapFormatAction(toggleSelectionAsBlockquote), + 'meta+7': wrapFormatAction(formatCodeBlock), + 'meta+e': wrapFormatAction(formatCodeBlock), + 'meta+q': wrapFormatAction(toggleSelectionAsBlockquote), + 'meta+9': wrapFormatAction(cycleSelectionCalloutFormats), +}; + +function createKeyDownListener(context: EditorUiContext, useExtended: boolean): (e: KeyboardEvent) => void { + const keySetToUse = useExtended ? extendedActionsByKeys : baseActionsByKeys; return (event: KeyboardEvent) => { const combo = keyboardEventToKeyComboString(event); // console.log(`pressed: ${combo}`); - if (actionsByKeys[combo]) { - const handled = actionsByKeys[combo](context.editor, context); + if (keySetToUse[combo]) { + const handled = keySetToUse[combo](context.editor, context); if (handled) { event.stopPropagation(); event.preventDefault(); @@ -127,8 +134,8 @@ function overrideDefaultCommands(editor: LexicalEditor) { }, COMMAND_PRIORITY_HIGH); } -export function registerShortcuts(context: EditorUiContext) { - const listener = createKeyDownListener(context); +export function registerShortcuts(context: EditorUiContext, useExtended: boolean) { + const listener = createKeyDownListener(context, useExtended); overrideDefaultCommands(context.editor); return context.editor.registerRootListener((rootElement: null | HTMLElement, prevRootElement: null | HTMLElement) => { diff --git a/resources/js/wysiwyg/ui/defaults/toolbars.ts b/resources/js/wysiwyg/ui/defaults/toolbars.ts index d6af996384b..a3ada5c89f6 100644 --- a/resources/js/wysiwyg/ui/defaults/toolbars.ts +++ b/resources/js/wysiwyg/ui/defaults/toolbars.ts @@ -227,6 +227,7 @@ export function getBasicEditorToolbar(context: EditorUiContext): EditorContainer new EditorButton(bold), new EditorButton(italic), new EditorButton(link), + new EditorButton(code), new EditorButton(bulletList), new EditorButton(numberList), ]) diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php index 6082c59de61..c0d4fbc63e6 100644 --- a/tests/Entity/BookTest.php +++ b/tests/Entity/BookTest.php @@ -256,8 +256,8 @@ public function test_description_limited_to_specific_html() { $book = $this->entities->book(); - $input = '

    Test

    Contenta

    Hello

    '; - $expected = '

    Contenta

    '; + $input = '

    Test

    Contenta

    Hello
    code

    '; + $expected = '

    Contentacode

    '; $this->asEditor()->put($book->getUrl(), [ 'name' => $book->name, From a2bb5bdf10bbee40a6eb12e40101ea61f0a92021 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 17 Apr 2026 21:22:04 +0100 Subject: [PATCH 113/204] Meta: Updated COC, templates, PR template for community rules Added reference to new community rules page where sensible. --- .github/CODE_OF_CONDUCT.md | 86 +--------------------- .github/ISSUE_TEMPLATE/feature_request.yml | 10 +++ .github/pull_request_template.md | 11 +++ readme.md | 9 ++- 4 files changed, 28 insertions(+), 88 deletions(-) create mode 100644 .github/pull_request_template.md diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 2c6317af654..7a02656725d 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,84 +1,2 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -education, socio-economic status, nationality, personal appearance, race, -religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -### Project Maintainer Standards - -Project maintainers should generally follow these additional standards: - -* Avoid using a negative or harsh tone in communication, Even if the other party -is being negative themselves. -* When providing criticism, try to make it constructive to lead the other person -down the correct path. -* Keep the [project definition](https://github.com/BookStackApp/BookStack#project-definition) -in mind when deciding what's in scope of the Project. - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. In addition, Project -maintainers are responsible for following the standards themselves. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at the email address shown on [the profile here](https://github.com/ssddanbrown). All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org +Please find our community rules on our website here: +https://www.bookstackapp.com/about/community-rules/ \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 0ebb8e72f29..ca1f2b8301c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -56,3 +56,13 @@ body: description: Add any other context or screenshots about the feature request here. validations: required: false + - type: checkboxes + id: ai-thoughts + attributes: + label: Have you used generative AI/LLMs to create any thoughts in this request? + description: | + We ask that no machine generated thoughts or ideas are provided, to avoid us spending time considering the ideas + of a machine instead of a human. Further guidance on this can be found [in the BookStack community rules](https://www.bookstackapp.com/about/community-rules/#use-of-llmsai). + options: + - label: This request only contains the thoughts & ideas of a human + required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000000..70f1058748c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,11 @@ +## Details + + + + +## Checklist + + + +- [ ] 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. diff --git a/readme.md b/readme.md index 00eb135046e..d3a408ad02c 100644 --- a/readme.md +++ b/readme.md @@ -9,8 +9,9 @@
    [![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/) -[![Discord](https://img.shields.io/static/v1?label=Discord&message=chat&color=738adb&logo=discord)](https://www.bookstackapp.com/links/discord) +[![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)
    [![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) @@ -20,11 +21,10 @@ A platform for storing and organising information and documentation. Details for * [Installation Instructions](https://www.bookstackapp.com/docs/admin/installation) * [Documentation](https://www.bookstackapp.com/docs) * [Demo Instance](https://demo.bookstackapp.com) - * [Admin Login](https://demo.bookstackapp.com/login?email=admin@example.com&password=password) * [Screenshots](https://www.bookstackapp.com/#screenshots) * [BookStack Blog](https://www.bookstackapp.com/blog) * [Issue List](https://github.com/BookStackApp/BookStack/issues) -* [Discord Chat](https://www.bookstackapp.com/links/discord) +* [Community Discussions](https://community.bookstackapp.com/) * [Support Options](https://www.bookstackapp.com/support/) ## 📚 Project Definition @@ -124,8 +124,9 @@ Feel free to [create issues](https://github.com/BookStackApp/BookStack/issues/ne 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. Pull requests should be created from the `development` branch since they will be merged back into `development` once done. Please do not build from or request a merge into the `release` branch as this is only for publishing releases. If you are looking to alter CSS or JavaScript content please edit the source files found in `resources/`. Any CSS or JS files within `public` are built from these source files and therefore should not be edited directly. +See the [Development & Testing](#-development--testing) section above for further development guidance. -The project's code of conduct [can be found here](https://github.com/BookStackApp/BookStack/blob/development/.github/CODE_OF_CONDUCT.md). +The project's community rules, including those for raising issues and making code contributions, [can be found here](https://www.bookstackapp.com/about/community-rules/). ## 🔒 Security From 083fb1a600f1eeb8acb719675bdeb089c86ceee1 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 18 Apr 2026 20:43:27 +0100 Subject: [PATCH 114/204] Maintenance: Updated $request->get instance to use input --- .../Controllers/ForgotPasswordController.php | 4 ++-- app/Access/Controllers/LoginController.php | 8 ++++---- .../Controllers/MfaBackupCodesController.php | 2 +- app/Access/Controllers/MfaController.php | 4 ++-- .../Controllers/ResetPasswordController.php | 4 ++-- app/Access/Controllers/Saml2Controller.php | 4 ++-- app/Access/Controllers/SocialController.php | 2 +- .../Controllers/UserInviteController.php | 2 +- .../Controllers/AuditLogController.php | 14 ++++++------- .../Controllers/FavouriteController.php | 2 +- app/Activity/Controllers/TagController.php | 6 +++--- app/Api/UserApiTokenController.php | 8 ++++---- app/Entities/Controllers/BookController.php | 4 ++-- .../Controllers/BookshelfApiController.php | 4 ++-- .../Controllers/BookshelfController.php | 4 ++-- .../Controllers/ChapterApiController.php | 2 +- .../Controllers/ChapterController.php | 6 +++--- .../Controllers/PageApiController.php | 8 ++++---- app/Entities/Controllers/PageController.php | 8 ++++---- .../Controllers/PageTemplateController.php | 4 ++-- app/Entities/Tools/PermissionsUpdater.php | 4 ++-- app/Search/SearchApiController.php | 6 +++--- app/Search/SearchController.php | 20 +++++++++---------- app/Search/SearchOptions.php | 2 +- app/Settings/AppSettingsStore.php | 4 ++-- app/Settings/MaintenanceController.php | 2 +- app/Sorting/BookSortController.php | 4 ++-- .../Controllers/AttachmentApiController.php | 4 ++-- .../Controllers/AttachmentController.php | 16 +++++++-------- .../Controllers/DrawioImageController.php | 12 +++++------ .../Controllers/GalleryImageController.php | 10 +++++----- app/Users/Controllers/RoleController.php | 4 ++-- .../Controllers/UserAccountController.php | 6 +++--- app/Users/Controllers/UserApiController.php | 2 +- app/Users/Controllers/UserController.php | 4 ++-- .../Controllers/UserPreferencesController.php | 8 ++++---- .../Controllers/UserSearchController.php | 4 ++-- app/Util/SimpleListOptions.php | 2 +- 38 files changed, 107 insertions(+), 107 deletions(-) diff --git a/app/Access/Controllers/ForgotPasswordController.php b/app/Access/Controllers/ForgotPasswordController.php index 36dd977558b..e8127e6173a 100644 --- a/app/Access/Controllers/ForgotPasswordController.php +++ b/app/Access/Controllers/ForgotPasswordController.php @@ -45,11 +45,11 @@ public function sendResetLinkEmail(Request $request) ); if ($response === Password::RESET_LINK_SENT) { - $this->logActivity(ActivityType::AUTH_PASSWORD_RESET, $request->get('email')); + $this->logActivity(ActivityType::AUTH_PASSWORD_RESET, $request->input('email')); } if (in_array($response, [Password::RESET_LINK_SENT, Password::INVALID_USER, Password::RESET_THROTTLED])) { - $message = trans('auth.reset_password_sent', ['email' => $request->get('email')]); + $message = trans('auth.reset_password_sent', ['email' => $request->input('email')]); $this->showSuccessNotification($message); return redirect('/password/email')->with('status', trans($response)); diff --git a/app/Access/Controllers/LoginController.php b/app/Access/Controllers/LoginController.php index ce872ba88dc..4694f22e4d3 100644 --- a/app/Access/Controllers/LoginController.php +++ b/app/Access/Controllers/LoginController.php @@ -32,12 +32,12 @@ public function getLogin(Request $request) { $socialDrivers = $this->socialDriverManager->getActive(); $authMethod = config('auth.method'); - $preventInitiation = $request->get('prevent_auto_init') === 'true'; + $preventInitiation = $request->input('prevent_auto_init') === 'true'; if ($request->has('email')) { session()->flashInput([ - 'email' => $request->get('email'), - 'password' => (config('app.env') === 'demo') ? $request->get('password', '') : '', + 'email' => $request->input('email'), + 'password' => (config('app.env') === 'demo') ? $request->input('password', '') : '', ]); } @@ -62,7 +62,7 @@ public function getLogin(Request $request) public function login(Request $request) { $this->validateLogin($request); - $username = $request->get($this->username()); + $username = $request->input($this->username()); // Check login throttling attempts to see if they've gone over the limit if ($this->hasTooManyLoginAttempts($request)) { diff --git a/app/Access/Controllers/MfaBackupCodesController.php b/app/Access/Controllers/MfaBackupCodesController.php index 5c334674e7d..0a6416a1795 100644 --- a/app/Access/Controllers/MfaBackupCodesController.php +++ b/app/Access/Controllers/MfaBackupCodesController.php @@ -84,7 +84,7 @@ function ($attribute, $value, $fail) use ($codeService, $codes) { ], ]); - $updatedCodes = $codeService->removeInputCodeFromSet($request->get('code'), $codes); + $updatedCodes = $codeService->removeInputCodeFromSet($request->input('code'), $codes); MfaValue::upsertWithValue($user, MfaValue::METHOD_BACKUP_CODES, $updatedCodes); $mfaSession->markVerifiedForUser($user); diff --git a/app/Access/Controllers/MfaController.php b/app/Access/Controllers/MfaController.php index c9100ef9120..181cfc0b84b 100644 --- a/app/Access/Controllers/MfaController.php +++ b/app/Access/Controllers/MfaController.php @@ -51,14 +51,14 @@ public function remove(string $method) */ public function verify(Request $request) { - $desiredMethod = $request->get('method'); + $desiredMethod = $request->input('method'); $userMethods = $this->currentOrLastAttemptedUser() ->mfaValues() ->get(['id', 'method']) ->groupBy('method'); // Basic search for the default option for a user. - // (Prioritises totp over backup codes) + // (Prioritises TOTP over backup codes) $method = $userMethods->has($desiredMethod) ? $desiredMethod : $userMethods->keys()->sort()->reverse()->first(); $otherMethods = $userMethods->keys()->filter(function ($userMethod) use ($method) { return $method !== $userMethod; diff --git a/app/Access/Controllers/ResetPasswordController.php b/app/Access/Controllers/ResetPasswordController.php index 3af65d17fb6..e81c98b288c 100644 --- a/app/Access/Controllers/ResetPasswordController.php +++ b/app/Access/Controllers/ResetPasswordController.php @@ -48,7 +48,7 @@ public function reset(Request $request) // Here we will attempt to reset the user's password. If it is successful we // will update the password on an actual user model and persist it to the - // database. Otherwise we will parse the error and return the response. + // database. Otherwise, we will parse the error and return the response. $credentials = $request->only('email', 'password', 'password_confirmation', 'token'); $response = Password::broker()->reset($credentials, function (User $user, string $password) { $user->password = Hash::make($password); @@ -63,7 +63,7 @@ public function reset(Request $request) // redirect them back to where they came from with their error message. return $response === Password::PASSWORD_RESET ? $this->sendResetResponse() - : $this->sendResetFailedResponse($request, $response, $request->get('token')); + : $this->sendResetFailedResponse($request, $response, $request->input('token')); } /** diff --git a/app/Access/Controllers/Saml2Controller.php b/app/Access/Controllers/Saml2Controller.php index 6f802370e9c..39598b1435a 100644 --- a/app/Access/Controllers/Saml2Controller.php +++ b/app/Access/Controllers/Saml2Controller.php @@ -78,7 +78,7 @@ public function sls() */ public function startAcs(Request $request) { - $samlResponse = $request->get('SAMLResponse', null); + $samlResponse = $request->input('SAMLResponse', null); if (empty($samlResponse)) { $this->showErrorNotification(trans('errors.saml_fail_authed', ['system' => config('saml2.name')])); @@ -100,7 +100,7 @@ public function startAcs(Request $request) */ public function processAcs(Request $request) { - $acsId = $request->get('id', null); + $acsId = $request->input('id', null); $cacheKey = 'saml2_acs:' . $acsId; $samlResponse = null; diff --git a/app/Access/Controllers/SocialController.php b/app/Access/Controllers/SocialController.php index 07f57062d01..5a090c7ca2a 100644 --- a/app/Access/Controllers/SocialController.php +++ b/app/Access/Controllers/SocialController.php @@ -67,7 +67,7 @@ public function callback(Request $request, string $socialDriver) if ($request->has('error') && $request->has('error_description')) { throw new SocialSignInException(trans('errors.social_login_bad_response', [ 'socialAccount' => $socialDriver, - 'error' => $request->get('error_description'), + 'error' => $request->input('error_description'), ]), '/login'); } diff --git a/app/Access/Controllers/UserInviteController.php b/app/Access/Controllers/UserInviteController.php index 9ee05b84fa9..091b68e5594 100644 --- a/app/Access/Controllers/UserInviteController.php +++ b/app/Access/Controllers/UserInviteController.php @@ -67,7 +67,7 @@ public function setPassword(Request $request, string $token) } $user = $this->userRepo->getById($userId); - $user->password = Hash::make($request->get('password')); + $user->password = Hash::make($request->input('password')); $user->email_confirmed = true; $user->save(); diff --git a/app/Activity/Controllers/AuditLogController.php b/app/Activity/Controllers/AuditLogController.php index c4f9b91edb8..ed1421c0d01 100644 --- a/app/Activity/Controllers/AuditLogController.php +++ b/app/Activity/Controllers/AuditLogController.php @@ -17,19 +17,19 @@ public function index(Request $request) $this->checkPermission(Permission::SettingsManage); $this->checkPermission(Permission::UsersManage); - $sort = $request->get('sort', 'activity_date'); - $order = $request->get('order', 'desc'); + $sort = $request->input('sort', 'activity_date'); + $order = $request->input('order', 'desc'); $listOptions = (new SimpleListOptions('', $sort, $order))->withSortOptions([ 'created_at' => trans('settings.audit_table_date'), 'type' => trans('settings.audit_table_event'), ]); $filters = [ - 'event' => $request->get('event', ''), - 'date_from' => $request->get('date_from', ''), - 'date_to' => $request->get('date_to', ''), - 'user' => $request->get('user', ''), - 'ip' => $request->get('ip', ''), + 'event' => $request->input('event', ''), + 'date_from' => $request->input('date_from', ''), + 'date_to' => $request->input('date_to', ''), + 'user' => $request->input('user', ''), + 'ip' => $request->input('ip', ''), ]; $query = Activity::query() diff --git a/app/Activity/Controllers/FavouriteController.php b/app/Activity/Controllers/FavouriteController.php index deeb4b0afb4..65bae276d28 100644 --- a/app/Activity/Controllers/FavouriteController.php +++ b/app/Activity/Controllers/FavouriteController.php @@ -20,7 +20,7 @@ public function __construct( public function index(Request $request, QueryTopFavourites $topFavourites) { $viewCount = 20; - $page = intval($request->get('page', 1)); + $page = intval($request->input('page', 1)); $favourites = $topFavourites->run($viewCount + 1, (($page - 1) * $viewCount)); $hasMoreLink = ($favourites->count() > $viewCount) ? url('/favourites?page=' . ($page + 1)) : null; diff --git a/app/Activity/Controllers/TagController.php b/app/Activity/Controllers/TagController.php index 723dc4ab474..b57c798254a 100644 --- a/app/Activity/Controllers/TagController.php +++ b/app/Activity/Controllers/TagController.php @@ -46,7 +46,7 @@ public function index(Request $request) */ public function getNameSuggestions(Request $request) { - $searchTerm = $request->get('search', ''); + $searchTerm = $request->input('search', ''); $suggestions = $this->tagRepo->getNameSuggestions($searchTerm); return response()->json($suggestions); @@ -57,8 +57,8 @@ public function getNameSuggestions(Request $request) */ public function getValueSuggestions(Request $request) { - $searchTerm = $request->get('search', ''); - $tagName = $request->get('name', ''); + $searchTerm = $request->input('search', ''); + $tagName = $request->input('name', ''); $suggestions = $this->tagRepo->getValueSuggestions($searchTerm, $tagName); return response()->json($suggestions); diff --git a/app/Api/UserApiTokenController.php b/app/Api/UserApiTokenController.php index 2ca9e22352e..2894ede3aa5 100644 --- a/app/Api/UserApiTokenController.php +++ b/app/Api/UserApiTokenController.php @@ -48,11 +48,11 @@ public function store(Request $request, int $userId) $secret = Str::random(32); $token = (new ApiToken())->forceFill([ - 'name' => $request->get('name'), + 'name' => $request->input('name'), 'token_id' => Str::random(32), 'secret' => Hash::make($secret), 'user_id' => $user->id, - 'expires_at' => $request->get('expires_at') ?: ApiToken::defaultExpiry(), + 'expires_at' => $request->input('expires_at') ?: ApiToken::defaultExpiry(), ]); while (ApiToken::query()->where('token_id', '=', $token->token_id)->exists()) { @@ -100,8 +100,8 @@ public function update(Request $request, int $userId, int $tokenId) [$user, $token] = $this->checkPermissionAndFetchUserToken($userId, $tokenId); $token->fill([ - 'name' => $request->get('name'), - 'expires_at' => $request->get('expires_at') ?: ApiToken::defaultExpiry(), + 'name' => $request->input('name'), + 'expires_at' => $request->input('expires_at') ?: ApiToken::defaultExpiry(), ])->save(); $this->logActivity(ActivityType::API_TOKEN_UPDATE, $token); diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php index fca530f8adf..98470d91ce8 100644 --- a/app/Entities/Controllers/BookController.php +++ b/app/Entities/Controllers/BookController.php @@ -144,7 +144,7 @@ public function show(Request $request, ActivityQueries $activities, string $slug View::incrementFor($book); if ($request->has('shelf')) { - $this->shelfContext->setShelfContext(intval($request->get('shelf'))); + $this->shelfContext->setShelfContext(intval($request->input('shelf'))); } $this->setPageTitle($book->getShortName()); @@ -263,7 +263,7 @@ public function copy(Request $request, Cloner $cloner, string $bookSlug) $this->checkOwnablePermission(Permission::BookView, $book); $this->checkPermission(Permission::BookCreateAll); - $newName = $request->get('name') ?: $book->name; + $newName = $request->input('name') ?: $book->name; $bookCopy = $cloner->cloneBook($book, $newName); $this->showSuccessNotification(trans('entities.books_copy_success')); diff --git a/app/Entities/Controllers/BookshelfApiController.php b/app/Entities/Controllers/BookshelfApiController.php index 735742060c5..e620eb59c29 100644 --- a/app/Entities/Controllers/BookshelfApiController.php +++ b/app/Entities/Controllers/BookshelfApiController.php @@ -49,7 +49,7 @@ public function create(Request $request) $this->checkPermission(Permission::BookshelfCreateAll); $requestData = $this->validate($request, $this->rules()['create']); - $bookIds = $request->get('books', []); + $bookIds = $request->input('books', []); $shelf = $this->bookshelfRepo->create($requestData, $bookIds); return response()->json($this->forJsonDisplay($shelf)); @@ -88,7 +88,7 @@ public function update(Request $request, string $id) $this->checkOwnablePermission(Permission::BookshelfUpdate, $shelf); $requestData = $this->validate($request, $this->rules()['update']); - $bookIds = $request->get('books', null); + $bookIds = $request->input('books', null); $shelf = $this->bookshelfRepo->update($shelf, $requestData, $bookIds); diff --git a/app/Entities/Controllers/BookshelfController.php b/app/Entities/Controllers/BookshelfController.php index f5f4a90bfe9..1e8b26b5156 100644 --- a/app/Entities/Controllers/BookshelfController.php +++ b/app/Entities/Controllers/BookshelfController.php @@ -94,7 +94,7 @@ public function store(Request $request) 'tags' => ['array'], ]); - $bookIds = explode(',', $request->get('books', '')); + $bookIds = explode(',', $request->input('books', '')); $shelf = $this->shelfRepo->create($validated, $bookIds); return redirect($shelf->getUrl()); @@ -196,7 +196,7 @@ public function update(Request $request, string $slug) unset($validated['image']); } - $bookIds = explode(',', $request->get('books', '')); + $bookIds = explode(',', $request->input('books', '')); $shelf = $this->shelfRepo->update($shelf, $validated, $bookIds); return redirect($shelf->getUrl()); diff --git a/app/Entities/Controllers/ChapterApiController.php b/app/Entities/Controllers/ChapterApiController.php index 6aa62f887c8..9e0c69b1776 100644 --- a/app/Entities/Controllers/ChapterApiController.php +++ b/app/Entities/Controllers/ChapterApiController.php @@ -64,7 +64,7 @@ public function create(Request $request) { $requestData = $this->validate($request, $this->rules['create']); - $bookId = $request->get('book_id'); + $bookId = $request->input('book_id'); $book = $this->entityQueries->books->findVisibleByIdOrFail(intval($bookId)); $this->checkOwnablePermission(Permission::ChapterCreate, $book); diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 878ee42b5ae..db2391599ab 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -203,7 +203,7 @@ public function move(Request $request, string $bookSlug, string $chapterSlug) $this->checkOwnablePermission(Permission::ChapterUpdate, $chapter); $this->checkOwnablePermission(Permission::ChapterDelete, $chapter); - $entitySelection = $request->get('entity_selection', null); + $entitySelection = $request->input('entity_selection', null); if ($entitySelection === null || $entitySelection === '') { return redirect($chapter->getUrl()); } @@ -248,7 +248,7 @@ public function copy(Request $request, Cloner $cloner, string $bookSlug, string { $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); - $entitySelection = $request->get('entity_selection') ?: null; + $entitySelection = $request->input('entity_selection') ?: null; $newParentBook = $entitySelection ? $this->entityQueries->findVisibleByStringIdentifier($entitySelection) : $chapter->getParent(); if (!$newParentBook instanceof Book) { @@ -259,7 +259,7 @@ public function copy(Request $request, Cloner $cloner, string $bookSlug, string $this->checkOwnablePermission(Permission::ChapterCreate, $newParentBook); - $newName = $request->get('name') ?: $chapter->name; + $newName = $request->input('name') ?: $chapter->name; $chapterCopy = $cloner->cloneChapter($chapter, $newParentBook, $newName); $this->showSuccessNotification(trans('entities.chapters_copy_success')); diff --git a/app/Entities/Controllers/PageApiController.php b/app/Entities/Controllers/PageApiController.php index 197018ccafe..38042e67058 100644 --- a/app/Entities/Controllers/PageApiController.php +++ b/app/Entities/Controllers/PageApiController.php @@ -74,9 +74,9 @@ public function create(Request $request) $this->validate($request, $this->rules['create']); if ($request->has('chapter_id')) { - $parent = $this->entityQueries->chapters->findVisibleByIdOrFail(intval($request->get('chapter_id'))); + $parent = $this->entityQueries->chapters->findVisibleByIdOrFail(intval($request->input('chapter_id'))); } else { - $parent = $this->entityQueries->books->findVisibleByIdOrFail(intval($request->get('book_id'))); + $parent = $this->entityQueries->books->findVisibleByIdOrFail(intval($request->input('book_id'))); } $this->checkOwnablePermission(Permission::PageCreate, $parent); @@ -133,9 +133,9 @@ public function update(Request $request, string $id) $parent = null; if ($request->has('chapter_id')) { - $parent = $this->entityQueries->chapters->findVisibleByIdOrFail(intval($request->get('chapter_id'))); + $parent = $this->entityQueries->chapters->findVisibleByIdOrFail(intval($request->input('chapter_id'))); } elseif ($request->has('book_id')) { - $parent = $this->entityQueries->books->findVisibleByIdOrFail(intval($request->get('book_id'))); + $parent = $this->entityQueries->books->findVisibleByIdOrFail(intval($request->input('book_id'))); } if ($parent && !$parent->matches($page->getParent())) { diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index 8778560e275..82edfbc2763 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -88,7 +88,7 @@ public function createAsGuest(Request $request, string $bookSlug, ?string $chapt $page = $this->pageRepo->getNewDraftPage($parent); $this->pageRepo->publishDraft($page, [ - 'name' => $request->get('name'), + 'name' => $request->input('name'), ]); return redirect($page->getUrl('/edit')); @@ -408,7 +408,7 @@ public function move(Request $request, string $bookSlug, string $pageSlug) $this->checkOwnablePermission(Permission::PageUpdate, $page); $this->checkOwnablePermission(Permission::PageDelete, $page); - $entitySelection = $request->get('entity_selection', null); + $entitySelection = $request->input('entity_selection', null); if ($entitySelection === null || $entitySelection === '') { return redirect($page->getUrl()); } @@ -453,7 +453,7 @@ public function copy(Request $request, Cloner $cloner, string $bookSlug, string $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission(Permission::PageView, $page); - $entitySelection = $request->get('entity_selection') ?: null; + $entitySelection = $request->input('entity_selection') ?: null; $newParent = $entitySelection ? $this->entityQueries->findVisibleByStringIdentifier($entitySelection) : $page->getParent(); if (!$newParent instanceof Book && !$newParent instanceof Chapter) { @@ -464,7 +464,7 @@ public function copy(Request $request, Cloner $cloner, string $bookSlug, string $this->checkOwnablePermission(Permission::PageCreate, $newParent); - $newName = $request->get('name') ?: $page->name; + $newName = $request->input('name') ?: $page->name; $pageCopy = $cloner->clonePage($page, $newParent, $newName); $this->showSuccessNotification(trans('entities.pages_copy_success')); diff --git a/app/Entities/Controllers/PageTemplateController.php b/app/Entities/Controllers/PageTemplateController.php index c0b97214856..9ff2fe0293e 100644 --- a/app/Entities/Controllers/PageTemplateController.php +++ b/app/Entities/Controllers/PageTemplateController.php @@ -21,8 +21,8 @@ public function __construct( */ public function list(Request $request) { - $page = $request->get('page', 1); - $search = $request->get('search', ''); + $page = $request->input('page', 1); + $search = $request->input('search', ''); $count = 10; $query = $this->pageQueries->visibleTemplates() diff --git a/app/Entities/Tools/PermissionsUpdater.php b/app/Entities/Tools/PermissionsUpdater.php index f3165b603e5..5770d02f186 100644 --- a/app/Entities/Tools/PermissionsUpdater.php +++ b/app/Entities/Tools/PermissionsUpdater.php @@ -20,8 +20,8 @@ class PermissionsUpdater */ public function updateFromPermissionsForm(Entity $entity, Request $request): void { - $permissions = $request->get('permissions', null); - $ownerId = $request->get('owned_by', null); + $permissions = $request->input('permissions', null); + $ownerId = $request->input('owned_by', null); $entity->permissions()->delete(); diff --git a/app/Search/SearchApiController.php b/app/Search/SearchApiController.php index 5de7a511036..3ecb955ae37 100644 --- a/app/Search/SearchApiController.php +++ b/app/Search/SearchApiController.php @@ -40,9 +40,9 @@ public function all(Request $request): JsonResponse { $this->validate($request, $this->rules['all']); - $options = SearchOptions::fromString($request->get('query') ?? ''); - $page = intval($request->get('page', '0')) ?: 1; - $count = min(intval($request->get('count', '0')) ?: 20, 100); + $options = SearchOptions::fromString($request->input('query') ?? ''); + $page = intval($request->input('page', '0')) ?: 1; + $count = min(intval($request->input('count', '0')) ?: 20, 100); $results = $this->searchRunner->searchEntities($options, 'all', $page, $count); $this->resultsFormatter->format($results['results']->all(), $options); diff --git a/app/Search/SearchController.php b/app/Search/SearchController.php index 348d44a427f..50a73910afe 100644 --- a/app/Search/SearchController.php +++ b/app/Search/SearchController.php @@ -24,7 +24,7 @@ public function search(Request $request, SearchResultsFormatter $formatter) { $searchOpts = SearchOptions::fromRequest($request); $fullSearchString = $searchOpts->toString(); - $page = intval($request->get('page', '0')) ?: 1; + $page = intval($request->input('page', '0')) ?: 1; $count = setting()->getInteger('lists-page-count-search', 18, 1, 1000); $results = $this->searchRunner->searchEntities($searchOpts, 'all', $page, $count); @@ -49,7 +49,7 @@ public function search(Request $request, SearchResultsFormatter $formatter) */ public function searchBook(Request $request, int $bookId) { - $term = $request->get('term', ''); + $term = $request->input('term', ''); $results = $this->searchRunner->searchBook($bookId, $term); return view('entities.list', ['entities' => $results]); @@ -60,7 +60,7 @@ public function searchBook(Request $request, int $bookId) */ public function searchChapter(Request $request, int $chapterId) { - $term = $request->get('term', ''); + $term = $request->input('term', ''); $results = $this->searchRunner->searchChapter($chapterId, $term); return view('entities.list', ['entities' => $results]); @@ -72,9 +72,9 @@ public function searchChapter(Request $request, int $chapterId) */ public function searchForSelector(Request $request, QueryPopular $queryPopular) { - $entityTypes = $request->filled('types') ? explode(',', $request->get('types')) : ['page', 'chapter', 'book']; - $searchTerm = $request->get('term', false); - $permission = $request->get('permission', 'view'); + $entityTypes = $request->filled('types') ? explode(',', $request->input('types')) : ['page', 'chapter', 'book']; + $searchTerm = $request->input('term', false); + $permission = $request->input('permission', 'view'); // Search for entities otherwise show most popular if ($searchTerm !== false) { @@ -93,7 +93,7 @@ public function searchForSelector(Request $request, QueryPopular $queryPopular) */ public function templatesForSelector(Request $request) { - $searchTerm = $request->get('term', false); + $searchTerm = $request->input('term', false); if ($searchTerm !== false) { $searchOptions = SearchOptions::fromString($searchTerm); @@ -119,7 +119,7 @@ public function templatesForSelector(Request $request) */ public function searchSuggestions(Request $request) { - $searchTerm = $request->get('term', ''); + $searchTerm = $request->input('term', ''); $entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 5)['results']; foreach ($entities as $entity) { @@ -136,8 +136,8 @@ public function searchSuggestions(Request $request) */ public function searchSiblings(Request $request, SiblingFetcher $siblingFetcher) { - $type = $request->get('entity_type', null); - $id = $request->get('entity_id', null); + $type = $request->input('entity_type', null); + $id = $request->input('entity_id', null); $entities = $siblingFetcher->fetch($type, $id); diff --git a/app/Search/SearchOptions.php b/app/Search/SearchOptions.php index cfd068386ef..f3eb58b6b04 100644 --- a/app/Search/SearchOptions.php +++ b/app/Search/SearchOptions.php @@ -51,7 +51,7 @@ public static function fromRequest(Request $request): self } if ($request->has('term')) { - return static::fromString($request->get('term')); + return static::fromString($request->input('term')); } $instance = new SearchOptions(); diff --git a/app/Settings/AppSettingsStore.php b/app/Settings/AppSettingsStore.php index e098d87f8e4..9c370ca272a 100644 --- a/app/Settings/AppSettingsStore.php +++ b/app/Settings/AppSettingsStore.php @@ -44,7 +44,7 @@ protected function updateAppIcon(Request $request): void } // Clear icon image if requested - if ($request->get('app_icon_reset')) { + if ($request->input('app_icon_reset')) { $this->destroyExistingSettingImage('app-icon'); setting()->remove('app-icon'); foreach ($sizes as $size) { @@ -67,7 +67,7 @@ protected function updateAppLogo(Request $request): void } // Clear logo image if requested - if ($request->get('app_logo_reset')) { + if ($request->input('app_logo_reset')) { $this->destroyExistingSettingImage('app-logo'); setting()->remove('app-logo'); } diff --git a/app/Settings/MaintenanceController.php b/app/Settings/MaintenanceController.php index b2b2226bf98..64e6c011187 100644 --- a/app/Settings/MaintenanceController.php +++ b/app/Settings/MaintenanceController.php @@ -38,7 +38,7 @@ public function cleanupImages(Request $request, ImageService $imageService) $this->checkPermission(Permission::SettingsManage); $this->logActivity(ActivityType::MAINTENANCE_ACTION_RUN, 'cleanup-images'); - $checkRevisions = !($request->get('ignore_revisions', 'false') === 'true'); + $checkRevisions = !($request->input('ignore_revisions', 'false') === 'true'); $dryRun = !($request->has('confirm')); $imagesToDelete = $imageService->deleteUnusedImages($checkRevisions, $dryRun); diff --git a/app/Sorting/BookSortController.php b/app/Sorting/BookSortController.php index 7e2ee5465df..4ddbb14bc70 100644 --- a/app/Sorting/BookSortController.php +++ b/app/Sorting/BookSortController.php @@ -58,7 +58,7 @@ public function update(Request $request, BookSorter $sorter, string $bookSlug) // Sort via map if ($request->filled('sort-tree')) { (new DatabaseTransaction(function () use ($book, $request, $sorter, &$loggedActivityForBook) { - $sortMap = BookSortMap::fromJson($request->get('sort-tree')); + $sortMap = BookSortMap::fromJson($request->input('sort-tree')); $booksInvolved = $sorter->sortUsingMap($sortMap); // Add activity for involved books. @@ -72,7 +72,7 @@ public function update(Request $request, BookSorter $sorter, string $bookSlug) } if ($request->filled('auto-sort')) { - $sortSetId = intval($request->get('auto-sort')) ?: null; + $sortSetId = intval($request->input('auto-sort')) ?: null; if ($sortSetId && SortRule::query()->find($sortSetId) === null) { $sortSetId = null; } diff --git a/app/Uploads/Controllers/AttachmentApiController.php b/app/Uploads/Controllers/AttachmentApiController.php index ea3c4a962b3..2448b79b5d5 100644 --- a/app/Uploads/Controllers/AttachmentApiController.php +++ b/app/Uploads/Controllers/AttachmentApiController.php @@ -50,7 +50,7 @@ public function create(Request $request) $this->checkPermission(Permission::AttachmentCreateAll); $requestData = $this->validate($request, $this->rules()['create']); - $pageId = $request->get('uploaded_to'); + $pageId = $request->input('uploaded_to'); $page = $this->pageQueries->findVisibleByIdOrFail($pageId); $this->checkOwnablePermission(Permission::PageUpdate, $page); @@ -134,7 +134,7 @@ public function update(Request $request, string $id) $page = $attachment->page; if ($requestData['uploaded_to'] ?? false) { - $pageId = $request->get('uploaded_to'); + $pageId = $request->input('uploaded_to'); $page = $this->pageQueries->findVisibleByIdOrFail($pageId); $attachment->uploaded_to = $requestData['uploaded_to']; } diff --git a/app/Uploads/Controllers/AttachmentController.php b/app/Uploads/Controllers/AttachmentController.php index 9c60fa415f8..edcf066acd6 100644 --- a/app/Uploads/Controllers/AttachmentController.php +++ b/app/Uploads/Controllers/AttachmentController.php @@ -39,7 +39,7 @@ public function upload(Request $request) 'file' => array_merge(['required'], $this->attachmentService->getFileValidationRules()), ]); - $pageId = $request->get('uploaded_to'); + $pageId = $request->input('uploaded_to'); $page = $this->pageQueries->findVisibleByIdOrFail($pageId); $this->checkPermission(Permission::AttachmentCreateAll); @@ -125,8 +125,8 @@ public function update(Request $request, string $attachmentId) $this->checkOwnablePermission(Permission::AttachmentUpdate, $attachment); $attachment = $this->attachmentService->updateFile($attachment, [ - 'name' => $request->get('attachment_edit_name'), - 'link' => $request->get('attachment_edit_url'), + 'name' => $request->input('attachment_edit_name'), + 'link' => $request->input('attachment_edit_url'), ]); return view('attachments.manager-edit-form', [ @@ -141,7 +141,7 @@ public function update(Request $request, string $attachmentId) */ public function attachLink(Request $request) { - $pageId = $request->get('attachment_link_uploaded_to'); + $pageId = $request->input('attachment_link_uploaded_to'); try { $this->validate($request, [ @@ -161,8 +161,8 @@ public function attachLink(Request $request) $this->checkPermission(Permission::AttachmentCreateAll); $this->checkOwnablePermission(Permission::PageUpdate, $page); - $attachmentName = $request->get('attachment_link_name'); - $link = $request->get('attachment_link_url'); + $attachmentName = $request->input('attachment_link_name'); + $link = $request->input('attachment_link_url'); $this->attachmentService->saveNewFromLink($attachmentName, $link, intval($pageId)); return view('attachments.manager-link-form', [ @@ -198,7 +198,7 @@ public function sortForPage(Request $request, int $pageId) $page = $this->pageQueries->findVisibleByIdOrFail($pageId); $this->checkOwnablePermission(Permission::PageUpdate, $page); - $attachmentOrder = $request->get('order'); + $attachmentOrder = $request->input('order'); $this->attachmentService->updateFileOrderWithinPage($attachmentOrder, $pageId); return response()->json(['message' => trans('entities.attachments_order_updated')]); @@ -231,7 +231,7 @@ public function get(Request $request, string $attachmentId) $attachmentStream = $this->attachmentService->streamAttachmentFromStorage($attachment); $attachmentSize = $this->attachmentService->getAttachmentFileSize($attachment); - if ($request->get('open') === 'true') { + if ($request->input('open') === 'true') { return $this->download()->streamedInline($attachmentStream, $fileName, $attachmentSize); } diff --git a/app/Uploads/Controllers/DrawioImageController.php b/app/Uploads/Controllers/DrawioImageController.php index f44acd997d2..8295febc1c1 100644 --- a/app/Uploads/Controllers/DrawioImageController.php +++ b/app/Uploads/Controllers/DrawioImageController.php @@ -24,10 +24,10 @@ public function __construct( */ public function list(Request $request, ImageResizer $resizer) { - $page = $request->get('page', 1); - $searchTerm = $request->get('search', null); - $uploadedToFilter = $request->get('uploaded_to', null); - $parentTypeFilter = $request->get('filter_type', null); + $page = $request->input('page', 1); + $searchTerm = $request->input('search', null); + $uploadedToFilter = $request->input('uploaded_to', null); + $parentTypeFilter = $request->input('filter_type', null); $imgData = $this->imageRepo->getEntityFiltered('drawio', $parentTypeFilter, $page, 24, $uploadedToFilter, $searchTerm); $viewData = [ @@ -59,10 +59,10 @@ public function create(Request $request) ]); $this->checkPermission(Permission::ImageCreateAll); - $imageBase64Data = $request->get('image'); + $imageBase64Data = $request->input('image'); try { - $uploadedTo = $request->get('uploaded_to', 0); + $uploadedTo = $request->input('uploaded_to', 0); $image = $this->imageRepo->saveDrawing($imageBase64Data, $uploadedTo); } catch (ImageUploadException $e) { return response($e->getMessage(), 500); diff --git a/app/Uploads/Controllers/GalleryImageController.php b/app/Uploads/Controllers/GalleryImageController.php index 745efcde812..908322be07f 100644 --- a/app/Uploads/Controllers/GalleryImageController.php +++ b/app/Uploads/Controllers/GalleryImageController.php @@ -24,10 +24,10 @@ public function __construct( */ public function list(Request $request, ImageResizer $resizer) { - $page = $request->get('page', 1); - $searchTerm = $request->get('search', null); - $uploadedToFilter = $request->get('uploaded_to', null); - $parentTypeFilter = $request->get('filter_type', null); + $page = $request->input('page', 1); + $searchTerm = $request->input('search', null); + $uploadedToFilter = $request->input('uploaded_to', null); + $parentTypeFilter = $request->input('filter_type', null); $imgData = $this->imageRepo->getEntityFiltered('gallery', $parentTypeFilter, $page, 30, $uploadedToFilter, $searchTerm); $viewData = [ @@ -69,7 +69,7 @@ public function create(Request $request) try { $imageUpload = $request->file('file'); - $uploadedTo = $request->get('uploaded_to', 0); + $uploadedTo = $request->input('uploaded_to', 0); $image = $this->imageRepo->saveNew($imageUpload, 'gallery', $uploadedTo); } catch (ImageUploadException $e) { return response($e->getMessage(), 500); diff --git a/app/Users/Controllers/RoleController.php b/app/Users/Controllers/RoleController.php index 549f6e0ac8f..b9f06dace84 100644 --- a/app/Users/Controllers/RoleController.php +++ b/app/Users/Controllers/RoleController.php @@ -55,7 +55,7 @@ public function create(Request $request) /** @var ?Role $role */ $role = null; if ($request->has('copy_from')) { - $role = Role::query()->find($request->get('copy_from')); + $role = Role::query()->find($request->input('copy_from')); } if ($role) { @@ -150,7 +150,7 @@ public function delete(Request $request, string $id) $this->checkPermission(Permission::UserRolesManage); try { - $migrateRoleId = intval($request->get('migrate_role_id') ?: "0"); + $migrateRoleId = intval($request->input('migrate_role_id') ?: "0"); $this->permissionsRepo->deleteRole($id, $migrateRoleId); } catch (PermissionsException $e) { $this->showErrorNotification($e->getMessage()); diff --git a/app/Users/Controllers/UserAccountController.php b/app/Users/Controllers/UserAccountController.php index a8baba5294b..21816d5b89b 100644 --- a/app/Users/Controllers/UserAccountController.php +++ b/app/Users/Controllers/UserAccountController.php @@ -106,8 +106,8 @@ public function showShortcuts() */ public function updateShortcuts(Request $request) { - $enabled = $request->get('enabled') === 'true'; - $providedShortcuts = $request->get('shortcut', []); + $enabled = $request->input('enabled') === 'true'; + $providedShortcuts = $request->input('shortcut', []); $shortcuts = new UserShortcutMap($providedShortcuts); setting()->putForCurrentUser('ui-shortcuts', $shortcuts->toJson()); @@ -218,7 +218,7 @@ public function destroy(Request $request) { $this->preventAccessInDemoMode(); - $requestNewOwnerId = intval($request->get('new_owner_id')) ?: null; + $requestNewOwnerId = intval($request->input('new_owner_id')) ?: null; $newOwnerId = userCan(Permission::UsersManage) ? $requestNewOwnerId : null; $this->userRepo->destroy(user(), $newOwnerId); diff --git a/app/Users/Controllers/UserApiController.php b/app/Users/Controllers/UserApiController.php index 25753280f17..ebc17e262f3 100644 --- a/app/Users/Controllers/UserApiController.php +++ b/app/Users/Controllers/UserApiController.php @@ -141,7 +141,7 @@ public function update(Request $request, string $id) public function delete(Request $request, string $id) { $user = $this->userRepo->getById($id); - $newOwnerId = $request->get('migrate_ownership_id', null); + $newOwnerId = $request->input('migrate_ownership_id', null); $this->userRepo->destroy($user, $newOwnerId); diff --git a/app/Users/Controllers/UserController.php b/app/Users/Controllers/UserController.php index 494221b143e..f93c00a89c2 100644 --- a/app/Users/Controllers/UserController.php +++ b/app/Users/Controllers/UserController.php @@ -77,7 +77,7 @@ public function store(Request $request) $this->checkPermission(Permission::UsersManage); $authMethod = config('auth.method'); - $sendInvite = ($request->get('send_invite', 'false') === 'true'); + $sendInvite = ($request->input('send_invite', 'false') === 'true'); $externalAuth = $authMethod === 'ldap' || $authMethod === 'saml2' || $authMethod === 'oidc'; $passwordRequired = ($authMethod === 'standard' && !$sendInvite); @@ -202,7 +202,7 @@ public function destroy(Request $request, int $id) $this->checkPermission(Permission::UsersManage); $user = $this->userRepo->getById($id); - $newOwnerId = intval($request->get('new_owner_id')) ?: null; + $newOwnerId = intval($request->input('new_owner_id')) ?: null; $this->userRepo->destroy($user, $newOwnerId); diff --git a/app/Users/Controllers/UserPreferencesController.php b/app/Users/Controllers/UserPreferencesController.php index 0bed2d22a43..f4a56b7bf0e 100644 --- a/app/Users/Controllers/UserPreferencesController.php +++ b/app/Users/Controllers/UserPreferencesController.php @@ -23,7 +23,7 @@ public function changeView(Request $request, string $type) return $this->redirectToRequest($request); } - $view = $request->get('view'); + $view = $request->input('view'); if (!in_array($view, ['grid', 'list'])) { $view = 'list'; } @@ -44,8 +44,8 @@ public function changeSort(Request $request, string $type) return $this->redirectToRequest($request); } - $sort = substr($request->get('sort') ?: 'name', 0, 50); - $order = $request->get('order') === 'desc' ? 'desc' : 'asc'; + $sort = substr($request->input('sort') ?: 'name', 0, 50); + $order = $request->input('order') === 'desc' ? 'desc' : 'asc'; $sortKey = $type . '_sort'; $orderKey = $type . '_sort_order'; @@ -76,7 +76,7 @@ public function changeExpansion(Request $request, string $type) return response('Invalid key', 500); } - $newState = $request->get('expand', 'false'); + $newState = $request->input('expand', 'false'); setting()->putForCurrentUser('section_expansion#' . $type, $newState); return response('', 204); diff --git a/app/Users/Controllers/UserSearchController.php b/app/Users/Controllers/UserSearchController.php index bc0543cab16..9734255e7e0 100644 --- a/app/Users/Controllers/UserSearchController.php +++ b/app/Users/Controllers/UserSearchController.php @@ -26,7 +26,7 @@ public function forSelect(Request $request) $this->showPermissionError(); } - $search = $request->get('search', ''); + $search = $request->input('search', ''); $query = User::query() ->orderBy('name', 'asc') ->take(20); @@ -58,7 +58,7 @@ public function forMentions(Request $request) $this->showPermissionError(); } - $search = $request->get('search', ''); + $search = $request->input('search', ''); $query = User::query() ->orderBy('name', 'asc') ->take(20); diff --git a/app/Util/SimpleListOptions.php b/app/Util/SimpleListOptions.php index 81d8a587636..9fb1b98ae8f 100644 --- a/app/Util/SimpleListOptions.php +++ b/app/Util/SimpleListOptions.php @@ -30,7 +30,7 @@ public function __construct(string $typeKey, string $sort, string $order, string */ public static function fromRequest(Request $request, string $typeKey, bool $sortDescDefault = false): self { - $search = $request->get('search', ''); + $search = $request->input('search', ''); $sort = setting()->getForCurrentUser($typeKey . '_sort', ''); $order = setting()->getForCurrentUser($typeKey . '_sort_order', $sortDescDefault ? 'desc' : 'asc'); From befa3a8fbb23cb8026fc646b9c8e8617e095b91e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 19 Apr 2026 12:41:11 +0100 Subject: [PATCH 115/204] Permissions: Started addition of revision-view permission --- .../Controllers/PageRevisionController.php | 7 ++++++ app/Permissions/Permission.php | 2 ++ lang/en/settings.php | 1 + resources/views/entities/meta.blade.php | 2 +- .../show-sidebar-section-actions.blade.php | 10 +++++---- .../views/settings/roles/parts/form.blade.php | 1 + .../parts/revisions-permissions-row.blade.php | 22 +++++++++++++++++++ 7 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 resources/views/settings/roles/parts/revisions-permissions-row.blade.php diff --git a/app/Entities/Controllers/PageRevisionController.php b/app/Entities/Controllers/PageRevisionController.php index 4bc15e6e967..0d690cb2c33 100644 --- a/app/Entities/Controllers/PageRevisionController.php +++ b/app/Entities/Controllers/PageRevisionController.php @@ -34,6 +34,7 @@ public function __construct( */ public function index(Request $request, string $bookSlug, string $pageSlug) { + $this->checkPermission(Permission::RevisionViewAll); $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $listOptions = SimpleListOptions::fromRequest($request, 'page_revisions', true)->withSortOptions([ 'id' => trans('entities.pages_revisions_sort_number') @@ -65,6 +66,8 @@ public function index(Request $request, string $bookSlug, string $pageSlug) */ public function show(string $bookSlug, string $pageSlug, int $revisionId) { + $this->checkPermission(Permission::RevisionViewAll); + $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); /** @var ?PageRevision $revision */ $revision = $page->revisions()->where('id', '=', $revisionId)->first(); @@ -94,6 +97,8 @@ public function show(string $bookSlug, string $pageSlug, int $revisionId) */ public function changes(string $bookSlug, string $pageSlug, int $revisionId) { + $this->checkPermission(Permission::RevisionViewAll); + $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); /** @var ?PageRevision $revision */ $revision = $page->revisions()->where('id', '=', $revisionId)->first(); @@ -130,6 +135,7 @@ public function changes(string $bookSlug, string $pageSlug, int $revisionId) public function restore(string $bookSlug, string $pageSlug, int $revisionId) { $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); + $this->checkPermission(Permission::RevisionViewAll); $this->checkOwnablePermission(Permission::PageUpdate, $page); $page = $this->pageRepo->restoreRevision($page, $revisionId); @@ -145,6 +151,7 @@ public function restore(string $bookSlug, string $pageSlug, int $revisionId) public function destroy(string $bookSlug, string $pageSlug, int $revId) { $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); + $this->checkPermission(Permission::RevisionViewAll); $this->checkOwnablePermission(Permission::PageDelete, $page); $revision = $page->revisions()->where('id', '=', $revId)->first(); diff --git a/app/Permissions/Permission.php b/app/Permissions/Permission.php index 04878ada01f..0fbe9693dcb 100644 --- a/app/Permissions/Permission.php +++ b/app/Permissions/Permission.php @@ -118,6 +118,8 @@ enum Permission: string case PageViewAll = 'page-view-all'; case PageViewOwn = 'page-view-own'; + case RevisionViewAll = 'revision-view-all'; + /** * Get the generic permissions which may be queried for entities. */ diff --git a/lang/en/settings.php b/lang/en/settings.php index c4d1eb136eb..3937c650f86 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -207,6 +207,7 @@ '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', diff --git a/resources/views/entities/meta.blade.php b/resources/views/entities/meta.blade.php index 060c197a466..6c425a2401b 100644 --- a/resources/views/entities/meta.blade.php +++ b/resources/views/entities/meta.blade.php @@ -9,7 +9,7 @@
    @endif - @if ($entity->isA('page')) + @if ($entity->isA('page') && userCan(\BookStack\Permissions\Permission::RevisionViewAll)) @icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }} diff --git a/resources/views/pages/parts/show-sidebar-section-actions.blade.php b/resources/views/pages/parts/show-sidebar-section-actions.blade.php index ae115b69e23..94061ecb3a8 100644 --- a/resources/views/pages/parts/show-sidebar-section-actions.blade.php +++ b/resources/views/pages/parts/show-sidebar-section-actions.blade.php @@ -24,10 +24,12 @@ @endif @endif - - @icon('history') - {{ trans('entities.revisions') }} - + @if(userCan(\BookStack\Permissions\Permission::RevisionViewAll)) + + @icon('history') + {{ trans('entities.revisions') }} + + @endif @if(userCan(\BookStack\Permissions\Permission::RestrictionsManage, $page)) @icon('lock') diff --git a/resources/views/settings/roles/parts/form.blade.php b/resources/views/settings/roles/parts/form.blade.php index 5a9eca7d2cd..890f790574e 100644 --- a/resources/views/settings/roles/parts/form.blade.php +++ b/resources/views/settings/roles/parts/form.blade.php @@ -79,6 +79,7 @@ class="item-list toggle-switch-list"> @include('settings.roles.parts.asset-permissions-row', ['title' => trans('entities.books'), 'permissionPrefix' => 'book']) @include('settings.roles.parts.asset-permissions-row', ['title' => trans('entities.chapters'), 'permissionPrefix' => 'chapter']) @include('settings.roles.parts.asset-permissions-row', ['title' => trans('entities.pages'), 'permissionPrefix' => 'page']) + @include('settings.roles.parts.revisions-permissions-row', ['title' => trans('entities.revisions'), 'permissionPrefix' => 'revision']) @include('settings.roles.parts.related-asset-permissions-row', ['title' => trans('entities.images'), 'permissionPrefix' => 'image']) @include('settings.roles.parts.related-asset-permissions-row', ['title' => trans('entities.attachments'), 'permissionPrefix' => 'attachment']) @include('settings.roles.parts.related-asset-permissions-row', ['title' => trans('entities.comments'), 'permissionPrefix' => 'comment']) diff --git a/resources/views/settings/roles/parts/revisions-permissions-row.blade.php b/resources/views/settings/roles/parts/revisions-permissions-row.blade.php new file mode 100644 index 00000000000..fe886a5d0e1 --- /dev/null +++ b/resources/views/settings/roles/parts/revisions-permissions-row.blade.php @@ -0,0 +1,22 @@ +
    + +
    + {{ trans('common.create') }}
    + - +
    +
    + {{ trans('common.view') }}
    + @include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-view-all', 'label' => trans('settings.role_all')]) +
    +
    + {{ trans('common.edit') }}
    + - +
    +
    + {{ trans('common.delete') }}
    + {{ trans('settings.role_controlled_by_page_delete') }} +
    +
    \ No newline at end of file From 1339f668ebfd0155c15c122f4257e435d23f9a11 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 19 Apr 2026 15:32:10 +0100 Subject: [PATCH 116/204] Permissions: Added revision-view-all addition migration --- ...41616_add_revision_view_all_permission.php | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 database/migrations/2026_04_19_141616_add_revision_view_all_permission.php diff --git a/database/migrations/2026_04_19_141616_add_revision_view_all_permission.php b/database/migrations/2026_04_19_141616_add_revision_view_all_permission.php new file mode 100644 index 00000000000..5a0b9a09b40 --- /dev/null +++ b/database/migrations/2026_04_19_141616_add_revision_view_all_permission.php @@ -0,0 +1,67 @@ +insertGetId([ + 'name' => 'revision-view-all', + 'created_at' => Carbon::now()->toDateTimeString(), + 'updated_at' => Carbon::now()->toDateTimeString(), + ]); + + // Get ids of page view permissions + $pageViewPermissions = DB::table('role_permissions') + ->whereIn('name', [ + 'page-view-own', + 'page-view-all', + ])->get(); + + if (!$pageViewPermissions->count() === 0) { + return; + } + + // Get role ids which have page view permission + $applicableRoleIds = DB::table('permission_role') + ->whereIn('permission_id', $pageViewPermissions->pluck('id')) + ->pluck('role_id') + ->unique() + ->all(); + + // Assign the new permission to relevant roles + $newPermissionRoles = array_values(array_map(function (int $roleId) use ($permissionId) { + return [ + 'role_id' => $roleId, + 'permission_id' => $permissionId, + ]; + }, $applicableRoleIds)); + + DB::table('permission_role')->insert($newPermissionRoles); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Get the permission to remove + $revisionViewPermission = DB::table('role_permissions') + ->where('name', '=', 'revision-view-all') + ->first(); + + if (!$revisionViewPermission) { + return; + } + + // Remove the permission, and its use on roles, from the database + DB::table('permission_role')->where('permission_id', '=', $revisionViewPermission->id)->delete(); + DB::table('role_permissions')->where('id', '=', $revisionViewPermission->id)->delete(); + } +}; From e7e019d3d44b263031d4a63f91e4c6bdf3b424eb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 19 Apr 2026 15:56:54 +0100 Subject: [PATCH 117/204] Permissions: Added testing coverage for revision-view-all --- tests/Entity/PageRevisionTest.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/Entity/PageRevisionTest.php b/tests/Entity/PageRevisionTest.php index 132a10fa4da..8b46e84a634 100644 --- a/tests/Entity/PageRevisionTest.php +++ b/tests/Entity/PageRevisionTest.php @@ -4,6 +4,8 @@ use BookStack\Activity\ActivityType; use BookStack\Entities\Models\Page; +use BookStack\Entities\Models\PageRevision; +use BookStack\Permissions\Permission; use Tests\TestCase; class PageRevisionTest extends TestCase @@ -257,6 +259,33 @@ public function test_revision_changes_view_filters_html_content() $revisionView->assertDontSee('dontwantthishere'); } + public function test_access_to_revision_operation_requires_revision_view_all_permission() + { + $editor = $this->users->editor(); + $this->actingAs($editor); + + $page = $this->entities->page(); + $this->createRevisions($page, 3); + /** @var PageRevision $revision */ + $revision = $page->revisions()->orderBy('id', 'desc')->first(); + + $this->get($page->getUrl())->assertSee($page->getUrl('/revisions'), false); + $this->get($page->getUrl('/revisions'))->assertOk(); + $this->get($revision->getUrl())->assertOk(); + $this->get($revision->getUrl('/changes'))->assertOk(); + $this->put($revision->getUrl('/restore'))->assertRedirect($page->getUrl()); + $this->delete($revision->getUrl('/delete'))->assertRedirect($page->getUrl('/revisions')); + + $this->permissions->removeUserRolePermissions($editor, [Permission::RevisionViewAll]); + + $this->get($page->getUrl())->assertDontSee($page->getUrl('/revisions'), false); + $this->assertPermissionError($this->get($page->getUrl('/revisions'))); + $this->assertPermissionError($this->get($revision->getUrl())); + $this->assertPermissionError($this->get($revision->getUrl('/changes'))); + $this->assertPermissionError($this->put($revision->getUrl('/restore'))); + $this->assertPermissionError($this->delete($revision->getUrl('/delete'))); + } + public function test_revision_restore_action_only_visible_with_permission() { $page = $this->entities->page(); From ec0b0384a20f10a5ec44197a3fd5ca8f9fc543aa Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 19 Apr 2026 16:06:31 +0100 Subject: [PATCH 118/204] Permissions: Tweaks/fixed during review of revision-view-all changes --- app/Entities/Controllers/PageRevisionController.php | 4 ++-- .../2026_04_19_141616_add_revision_view_all_permission.php | 2 +- .../settings/roles/parts/revisions-permissions-row.blade.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Entities/Controllers/PageRevisionController.php b/app/Entities/Controllers/PageRevisionController.php index 0d690cb2c33..cc6b79bfe45 100644 --- a/app/Entities/Controllers/PageRevisionController.php +++ b/app/Entities/Controllers/PageRevisionController.php @@ -134,8 +134,8 @@ public function changes(string $bookSlug, string $pageSlug, int $revisionId) */ public function restore(string $bookSlug, string $pageSlug, int $revisionId) { - $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkPermission(Permission::RevisionViewAll); + $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission(Permission::PageUpdate, $page); $page = $this->pageRepo->restoreRevision($page, $revisionId); @@ -150,8 +150,8 @@ public function restore(string $bookSlug, string $pageSlug, int $revisionId) */ public function destroy(string $bookSlug, string $pageSlug, int $revId) { - $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkPermission(Permission::RevisionViewAll); + $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission(Permission::PageDelete, $page); $revision = $page->revisions()->where('id', '=', $revId)->first(); diff --git a/database/migrations/2026_04_19_141616_add_revision_view_all_permission.php b/database/migrations/2026_04_19_141616_add_revision_view_all_permission.php index 5a0b9a09b40..e4b51ff7026 100644 --- a/database/migrations/2026_04_19_141616_add_revision_view_all_permission.php +++ b/database/migrations/2026_04_19_141616_add_revision_view_all_permission.php @@ -24,7 +24,7 @@ public function up(): void 'page-view-all', ])->get(); - if (!$pageViewPermissions->count() === 0) { + if ($pageViewPermissions->count() === 0) { return; } diff --git a/resources/views/settings/roles/parts/revisions-permissions-row.blade.php b/resources/views/settings/roles/parts/revisions-permissions-row.blade.php index fe886a5d0e1..326925ef93c 100644 --- a/resources/views/settings/roles/parts/revisions-permissions-row.blade.php +++ b/resources/views/settings/roles/parts/revisions-permissions-row.blade.php @@ -19,4 +19,4 @@ {{ trans('common.delete') }}
    {{ trans('settings.role_controlled_by_page_delete') }} - \ No newline at end of file + From 426f9ac4934308da9f57580bc0e2fe399346cbb1 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 19 Apr 2026 16:23:16 +0100 Subject: [PATCH 119/204] Permissions: Prevent export revision metadata view without permission --- resources/views/exports/parts/meta.blade.php | 2 +- tests/Exports/HtmlExportTest.php | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/resources/views/exports/parts/meta.blade.php b/resources/views/exports/parts/meta.blade.php index 00117f4a157..07eff14a470 100644 --- a/resources/views/exports/parts/meta.blade.php +++ b/resources/views/exports/parts/meta.blade.php @@ -1,5 +1,5 @@
    - @if ($entity->isA('page')) + @if ($entity->isA('page') && userCan(\BookStack\Permissions\Permission::RevisionViewAll)) @icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }}
    @endif diff --git a/tests/Exports/HtmlExportTest.php b/tests/Exports/HtmlExportTest.php index f23352e0eb9..223a8c92285 100644 --- a/tests/Exports/HtmlExportTest.php +++ b/tests/Exports/HtmlExportTest.php @@ -5,6 +5,7 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; +use BookStack\Permissions\Permission; use Illuminate\Support\Facades\Storage; use Tests\TestCase; @@ -229,6 +230,20 @@ public function test_page_export_with_deleted_creator_and_updater() $resp->assertDontSee('ExportWizardTheFifth'); } + public function test_page_export_only_includes_revision_count_if_user_has_revision_view_permissions() + { + $editor = $this->users->editor(); + $page = $this->entities->page(); + + $resp = $this->actingAs($editor)->get($page->getUrl('/export/html')); + $resp->assertSee('Revision #'); + + $this->permissions->removeUserRolePermissions($editor, [Permission::RevisionViewAll]); + + $resp = $this->actingAs($editor)->get($page->getUrl('/export/html')); + $resp->assertDontSee('Revision #'); + } + public function test_html_exports_contain_csp_meta_tag() { $entities = [ From 4f370ccddb4f5fd8eddff5915a9413786ae2a2c7 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 20 Apr 2026 14:32:13 +0100 Subject: [PATCH 120/204] Styles: Aligned fonts set on content and headers for exports During review of #6069 --- resources/sass/export-styles.scss | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/resources/sass/export-styles.scss b/resources/sass/export-styles.scss index 22f15d1b82a..36901c0f122 100644 --- a/resources/sass/export-styles.scss +++ b/resources/sass/export-styles.scss @@ -12,12 +12,16 @@ html, body { } body { - font-family: 'DejaVu Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Roboto", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; margin: 0; padding: 0; display: block; } +// Set fonts to common system fonts, starting with DejaVu Sans due to support in DOMPDF +body, h1, h2, h3, h4, h5, h6 { + font-family: 'DejaVu Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Roboto", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; +} + table { border-spacing: 0; border-collapse: collapse; @@ -64,11 +68,6 @@ body.export-format-pdf { font-size: 14px; line-height: 1.2; - // Ensure heading glyph coverage for PDF engines that don't handle CSS vars well. - h1, h2, h3, h4, h5, h6 { - font-family: 'DejaVu Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Roboto", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; - } - h1, h2, h3, h4, h5, h6 { line-height: 1.2; } From e91747785b1950598812348ce9b8da89c7da9e22 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 20 Apr 2026 15:42:28 +0100 Subject: [PATCH 121/204] PDF: Started building system to allow custom DOMPDF font loading --- app/Config/exports.php | 4 +-- app/Exports/PdfGenerator.php | 47 +++++++++++++++++++++++++++ storage/fonts/.gitignore | 6 +++- storage/fonts/dompdf/.gitignore | 3 ++ storage/fonts/dompdf/cache/.gitignore | 2 ++ 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 storage/fonts/dompdf/.gitignore create mode 100644 storage/fonts/dompdf/cache/.gitignore diff --git a/app/Config/exports.php b/app/Config/exports.php index 2e22bc759e3..f48fe0a67a3 100644 --- a/app/Config/exports.php +++ b/app/Config/exports.php @@ -68,7 +68,7 @@ * Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic, * Symbol, ZapfDingbats. */ - 'font_dir' => storage_path('fonts/'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782) + 'font_dir' => storage_path('fonts/dompdf'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782) /** * The location of the DOMPDF font cache directory. @@ -78,7 +78,7 @@ * * Note: This directory must exist and be writable by the webserver process. */ - 'font_cache' => storage_path('fonts/'), + 'font_cache' => storage_path('fonts/dompdf/cache'), /** * The location of a temporary directory. diff --git a/app/Exports/PdfGenerator.php b/app/Exports/PdfGenerator.php index f31d8aad078..df40bf44f81 100644 --- a/app/Exports/PdfGenerator.php +++ b/app/Exports/PdfGenerator.php @@ -4,6 +4,8 @@ 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; @@ -60,12 +62,57 @@ protected function renderUsingDomPdf(string $html): string $domPdf = new Dompdf($options); $domPdf->setBasePath(base_path('public')); + $fontMetrics = $domPdf->getFontMetrics(); + $userFontfamilies = $this->getUserDomPdfFontFamilies(); + foreach ($userFontfamilies as $fontFamily => $fonts) { + $fontMetrics->setFontFamily($fontFamily, $fonts); + } + +// dd($userFontfamilies, $fontMetrics->getFontFamilies()); $domPdf->loadHTML($this->convertEntities($html)); $domPdf->render(); return (string) $domPdf->output(); } + /** + * @return array> + */ + 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(); + $font->saveAdobeFontMetrics($expectedUfm); + } + + $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 */ diff --git a/storage/fonts/.gitignore b/storage/fonts/.gitignore index c96a04f008e..cb0b47dace2 100755 --- a/storage/fonts/.gitignore +++ b/storage/fonts/.gitignore @@ -1,2 +1,6 @@ +# Font cache files have once been stored directly in this folder +# therefore its important the contents non-ignored by git +# are chosen selectively * -!.gitignore \ No newline at end of file +!.gitignore +!dompdf/ \ No newline at end of file diff --git a/storage/fonts/dompdf/.gitignore b/storage/fonts/dompdf/.gitignore new file mode 100644 index 00000000000..23ef65311b4 --- /dev/null +++ b/storage/fonts/dompdf/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!cache/ \ No newline at end of file diff --git a/storage/fonts/dompdf/cache/.gitignore b/storage/fonts/dompdf/cache/.gitignore new file mode 100644 index 00000000000..c96a04f008e --- /dev/null +++ b/storage/fonts/dompdf/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file From 241563e8fc272e9e12553462a72238d751ffe179 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 22 Apr 2026 13:12:34 +0100 Subject: [PATCH 122/204] Exports: Added testing coverage for DOMPDF font usage --- app/Exports/PdfGenerator.php | 1 - tests/Exports/PdfExportTest.php | 33 +++++++++++++++++++++++++ tests/test-data/fonts/Cardiff-Bold.ttf | Bin 0 -> 66940 bytes tests/test-data/fonts/Cardiff.ttf | Bin 0 -> 67984 bytes tests/test-data/fonts/attribution.txt | 2 ++ 5 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/test-data/fonts/Cardiff-Bold.ttf create mode 100644 tests/test-data/fonts/Cardiff.ttf create mode 100644 tests/test-data/fonts/attribution.txt diff --git a/app/Exports/PdfGenerator.php b/app/Exports/PdfGenerator.php index df40bf44f81..10f0624cfb3 100644 --- a/app/Exports/PdfGenerator.php +++ b/app/Exports/PdfGenerator.php @@ -68,7 +68,6 @@ protected function renderUsingDomPdf(string $html): string $fontMetrics->setFontFamily($fontFamily, $fonts); } -// dd($userFontfamilies, $fontMetrics->getFontFamilies()); $domPdf->loadHTML($this->convertEntities($html)); $domPdf->render(); diff --git a/tests/Exports/PdfExportTest.php b/tests/Exports/PdfExportTest.php index f311f8457db..78da3b0c2cc 100644 --- a/tests/Exports/PdfExportTest.php +++ b/tests/Exports/PdfExportTest.php @@ -79,6 +79,39 @@ public function test_page_pdf_export_opens_details_blocks() $this->assertStringContainsString('
    entities->page()->forceFill([ + 'html' => '

    Boldtext

    ', + ]); + $page->save(); + $this->setSettings([ + 'app-custom-head' => '' + ]); + $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(); diff --git a/tests/test-data/fonts/Cardiff-Bold.ttf b/tests/test-data/fonts/Cardiff-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..efaae4d9eb03a58d47a8f096ee53c1fb8c918664 GIT binary patch literal 66940 zcmd44d6*nmc`tlURdrQ&ZQZqWS5@!(GCechGd(>$i$)rac3GBWS(asEgh$dy8a&d- z8p*a;0%qU$Jq{4!03n3f$dU;OixUzcgb;8D4>y-flAD$AB!uwg;}Sgjey6H?Bs;nJ zp6~wksim2&?pn@y&%6BI?TAo9h)!NkcyjN7+gs!JJaO43QISeC zOe0jL3RS5_bsD8HYEY9})TVKopbmAZN0T%~(=0R`0`aF6MeLg)#UqJ7ri}W}>K~K^pdWxQ= zXXrA0A-#{T(6jU$U8U#g{qzD|qZjEV`T%{9K146m7tt5fm(Z8eU!X6eFQ*UFSI}3| zSJ79~*U;C}*U{J0H_$iIH_0|V9`e}NFK0&Y2YxGI_ z8TwoFv-G#=@6gZD&(kl^FVd&z@6z9+U!uQH|A2m(euaLO{vrJu{UiEy`p5Je^iSwF z>9^?9^iS!x>7UW>gx1b4o>&r>9y+nIcs6|hrPYf|Cy%el=PsQ+zI0)2`SdyQp#ow_Ld?=CE#K65d=xOQ>r z!t&aE!ugd;YoQaXXU{H*XD**Vvvf|xJ(gEbhSpXV*UktpTDq{xubx}tFFv>$x_IWo z(vom$_0k35)bayM{MzzEp|zz4md=Hi*iD6V%jcGGua(tvk+r3>%lTa z#TDuFg{4J27E@VTySTi1j$6F=f5H*U@e7M5?pwOZ<`g-OxBgJo{-^%@l*P3Z%gZO0 zFPyk^R=#xZB*=1N^}U%VjuNB)y!|4A2@S57YJH>iGm zacxOFwX%9)`J6gfIQCh1X=P>k{Mz!GcyjUd>7@&i;1^rgV6dlFmL5{D0vFG$UScaL z-hN{7!sUqPTSKB$icN zI?H~A{9#8fURXT2bawH=ed3z`Fn!};{-xaNQsl0a%S#uQFm5^E#*?cLo)Z|)g&7~M zfYHJyE?oehow&?jUOESAt=_kE4wRDCmQJvx4i9b+Ik9^FvVUJ?^}@+hU=zkQ{`^*! z&aV24DzB`bUOut7a&Glv2;W~?lP@ftUS>~PIw_uAJOP4;CzrrYU~aIle~JPIUAyG- z?ghW@R&dSju*t-%6p9S|q9 z6%2El+qxsXtv@7jM{x0u;Nl%OE{^ONTqW)a&e;>3v*)IB_6*M9cU(Lp?)M)j9tci9 z5S)JC#_8gLU_1u~SB1}BTDiD!N$i|)I97?1(0%`RRFE?&BEaRf^j zu%@^ioUIxbV`A3nP~{uDI-f35^LNL@p0L_U&w`7aS2Cb z)6*UN%5Pm+$sb~-VJ&}XX>IAk14}3I@!HZ-e)Qr)7xSlrn=TN>TAc;V#osZ$H9D<`)fJ~+}p%(LU0-ydOC5togPw%a#dvdyQ_6fVsDDL-= z|KwA+YQ_KT6rRbR$DVY7oW<37GWhHy&RWAaY|O{WCA?e4+2?S^B7U;FU-F+eirFpV zEO!1WycTfQ3XZmu!{i`F|Lkw`8{hx$K0o4*Wo_eWV;FfGuVc*-_Bc?r*FGhkyn z4l3vIzx+WtUI>X&I3g>m zrblCjY1#3F<9f+dI+M-i3&m2oQmxe+L(Sol(N=qGyfe|A>`hJ2%x;?7yk&mt!nW-@ zcJA7}XYVchZry+2;BAL)KYYiLJCELV_w(*~{;?O}+4n9UKXG#D)af(JFJvRRZ{_T{ z)${jXSi5)$ZUGSI)4}JKTKk_S&{_00R_VHi)#IJwyH$L^7k3IhBD^Fa#_T*=N>$AW8 zJD>af7ryw^@BZGGe*X`?{FSf%;n)7?>wo->Kl$djp8nHs{}~bJBY0{RgcOKKet}#E zT3!UseFygk+<(v8`D8wuFXpTHc7CQ%ESg2{`gNS2Cm#l`zIWq1EAQsh`MiJL^x(Yf zf5|5FU;kNVUzf>Wk-sK?yYc#acq2a|Kk;AxM)10m>9mKi&@Un{CNCi`#W-I^UQQk+ zuOP1^uOhD|uOY7`uOqLAE_)++6L~Xv3wbMf8+ki<2YDxX7kM}I+Iz|S$ot6$$S;yd z$S;u(k`Iv&lV668`xWvi`Bm~!@-gyp@@wQ1>#9w(nBSI86OD!E3U zB%dL_MLtV@oBR&>9Qi!?0{J3&iu^A5J@O^;`_R8%CSQSO{6q3J@<-(B3;4<>qVM4;GpLzlB@*^;do5;Q7zOG-qF0o}xa}l5VEvXdm`2RY+j)Zx1OT7;Mm84RS z#}}W-z3^Uh`B-*;_WsQN%<&_}@uS%Tnfo)f`;80S=f1V)HG7`MAG-UsyTA3VZ>|5$ zYoK?)g_-M*fX4qsHbF`6CFj|Uu8=t*H0NUpXQC^F>GUiYqplRiuoJ|q)yF&A_exTj z!?#=J0Rj??ZF7_ifhP;Qu?$Zv(76QHd}1eccXLgji6f+gZ^?ws&Ls&7=%9HHn-mz! zN#tm}3dX9oDB~iqTD$|L>%V-CtJb>y=T4`Kx23o!`LEtYk6mZsySOc7oJ1j;A0Eo* zO*3TejoF6j^!5!kO*IsX#fF-PCyJJ$L=wHzy|?+)7Bfm1|NaTY-*npd;9ets0M|=}rX4 zopFDOa28)}alSAPE@5h=H#tXfse{N+DN1oxsm6;EuJ)BoZ?faBevV_y?5{kpS#uK; zxm>k2)M*Z7($T2rz0r)os>hsEI+50N(<_wzm!X!bF>`*blZi=@T6Jh=r<3%WlXKkq zM|<>*bzaQZhDObg*_p9QETL=Ra9HAaC;&;wOR6YKYJ#p+Dl;=P+w%p%a~t*1R&y+u zTffF@s*@}fYTabg^-`7Ad@j!&;ziRGE0yVNCY#<~C=Ly6-LlmbQ`|G};IJjXYQeP31zJlcHA)&(9jfD=5wZa8R97}bXy@Z6 z+3JJq4&I9!9X)dRkRfj7nmu2bv7k(Z$G31bvBZR$tJbi>TPXXBwS@eF8~XfE3rtG2 zB>6mq)wOdpSYwCr4=!ML^Iu}AX!UuD@sodt>K4Zt8lFCNxl$=Lca0|ER-#r(giM~s zWhG5HEf&hhOe_C&Q`V)J6|cPKeU@3Tw87HpL{k$vz1FQIvv&NK^+-5m#wz({J}D`- zG&!_$IwC}L-Ag)gMcLtfYJ6naOCD`HiRp4tt%c$tOQ4RfS1E5MB2&p&%=37GkEBbP zeDgbUBzCvQsO&0qam{lV2KvtiSs2b;*SBb@O_-)yy|yfbQtp&`ceO%=R4$+QQr!K=#IRIPxpjAZy4SOZQ|YJyGeF76^`o@I zJxMZT*5^e_}<27i8c{O2MMnnn;VIl-*}>p^H4okGzxqm{ z>q|0Q>ye{(-7%6*nz3+Lk@NZa&8>E^62&N~`q`z^qp4IeH#WLy^WleX-8D5?&ZI|L z)0=0@nF{nANnw0BjIT&G`QwWcB4~zbCty+ukPmFFphXSSKaQj`ylaOsaAuXRW-!2T zFhB+o{8wOr;x^RKK-oxf#)XqKm&-SsjbeEwpVw4M`H&z-v+42MHcjWVR5-K$z(hCg z8mYo~XV0GdrCe#!bF)rvHXWG_a}3OgkB1x&0KnuYPT4q zc{PdYP5RTjqTY3{&}>r}+5}V4P|p$!>EtXENT#a;4K6{uGWA>QYwa!E25_Q@IIh@u z;e5!te>Z152S)QnmWePEeB8CoD2A8A2+7V3&|{1CZ_%Z4!mFDpE2ed-L$!L9Lfnhd4=6 zLtl|$K|Vud)W0v%Y8&?jn1XKxiZV;t z$4UZ)&1Rk$ElGT*M_YGq|M*?oxGP^?CtvX}bm#iN(C=}NW6dYYN-)zhQeUv;MMiy9 zaff-5$1&s{hgccKsAV06cxsM2B`uxnzxJt_>*K(G1_ZexCdmvBW*Rw3K-dU0}M zd~|rIQ7_ApBn$vsH}(nuR^I|J5$+4S2>3kN8IK!G>;buo?7ObVSw=qB9O{&^Ik%nk zxUe117K_#Lmhn#3h(vx#QyV)4F|2A%O;$o7da1E{VM}W?nTp1eokItXw02ET*XpL- z7~0*e)DO>ex>whKdN`IZ>1HvPN|&*A^WeDO0!>n6#;1q^mE|Fe2phM-gndU>qyZZ~ zj$hyiMjlG62somLQJ3O{Zn1_J!x>$p1WoND&ClivQ}jX27G;sz(U|%3%?UdaIWwW@ zp2Ksz5i4`Io=th0maAdSiHY%ipL>in$e2GKEfj`08dZ+CakdQ1SUgU&g(vkWQ#XOo zUC~lS(7hMs;sy9%zA-4}i*NCpS;f50ALIB+Ir{dSc(s;gP17Y7Tea zwIdxX6{{6UM)c{q?X%9)`KTNb#a#=-y_s7(ji&4U(TD@Ykj)j2VT~|e{rZpT)1cp6 z-z$a)!~X&~!9g;-ETmN3LU&`}U|Rv~V)=Y}g;d%Yc7;(7{#KtInAO|h6J`o;whB&v z9yw}DfqA6IbGfo(47-l&c(x_9+TE$i$>E`-S1fi9kBx0_4>e0AHvzCyZ)CFq_sYYq z5nX*pC!6Kfay+UShS#=j(<~L+qjL+REvs>Op;ED|QfXAJCK9Gy_l}iJuT(0RAwxFS z=g%=;lf=pXK!!*RfdHC}`cVL)8zv8^;cD=c$DWAONHh?YCyB`+bFKO_2Wf1^K3ZG` z`#fRcWB4wOh1PJ&*FMx9i`vmGyQg5Y*U2q9*%w%uR{c~9(c91^2{wv{#VOFaZ z3v^HSbyt;aA*X}cVXO$jxOK*A(QJ+*a(cWcbg3Jgz$e=9+?JYv*${UK(;0hmcA6=! ziJKJHG-wb6nXagbQKq=mEpxNIE>v7NjHp52Lp+BYnaT?AL;xD&IT~P;K)f&Kd&7?LBgSn|7sd-Qwp zsIJR0=mab2UH@0aX&oI~p1)dOCO?SYa~Z()LuD3k{+U{B#dsgH7`gk@-#aiq@Q z#^FMoxp^3?G9TGA16vVlPqFa9fm?U&*uEfczU8Jxx)qBQ_=8u})C5~3Yx8XfZrQ`m zoZmDvH8~!IU)${6WMBL^4c5r_GX|{7;3Ts`;OV#n-;Eg@TmxVYy~8t<>0?O$qrm>; zlze`uK0e!bf06QFcMgL%?o7r{87RjZF9wgLpLN@1}34OT?pn495jn3x}}{?b)_~ zJO^sg4>djK_@iaV^PF;k?1J~ecdkdj>v_dK-t)Zm-wTeC$s$70{6A(KV!x}>`<*xsfOK~h_9bjx2JYIy00WE>> zf8YZn2-O$$_6XNSRD(;cKS1BGF3_JJ{M09pet+NhF(1&TK<%$AO9@L=q6JY6g#|mE8g5m}79!x8oV_UNTGmV@m0G>t$z~dvOe7w$B3ktPbNKwe z>p$ct;p3b_4(^=+oz@tfBIJIiv{Wt${k06*pGIu|3MoF1MV@XQ2dS=}y^p&UR{Gk5 zmsa`lB7*#<>FMVMmg9;lJjj$-?5rP$$l`YDV!9uMz5|3Dy5TqNz~NLt*K4QmTjtY4 z@NzD#-M=ct5Iq{8(mwqL$kg{|0+i}gdP5aHOBqh~YQ8=U>sfuw z&dv+Y>5OxS)tH_et<*di5?;{mtmmVe8c|eOjHzbRb&{!(;VqkY?cTL>Vj`O@mgdjA zV{X(eyJa<%bQ8_Fnd#|z-AU|#ds%HXj&9plEX87(Or;zWw(ZJ@mX%Doo@pY`(Q1s) zr=6IdaB9_I#6FD6o}vMo8I~GlA?K;ho2D97Cy`2xjqTWR+qUgHCvK05dc-Jc5jmtf zPQ5iB&W1&MHK|)zLs}gAzb;3dWa&xRH55# zBc@0aGJ$`WYMuJh>qjzXMDe_E*efnBMg>9lM9K41)ll@1T;~c;{{*oFR{uh{`?yCj zyE$?jd51qkEzv`f3`Kl~bQxF$4H7%R*0Eho?%fV}R3ACWA*avRq1&57pu^R%7T4?` zhB`aVby-kT-hb;Yd$w&Ao4an%=GuNL$#md*XIqTr5sJspxh+6S*SeiCK9a@7kix2&lZbO-G$ZXc#xV)%5tqf9gRfQf`Jsv`ak|u2w8TqS=iik zRE>-3rU@0>y8chxW8kq{`hJjFEZ2Oy;KCaSqD);xof!HVaM{%~v_O!V@;Pb*huZ^= z+6Z3s9f3fk1AQ4g6xCXhw65yr}kbG7fggK^VB*^pi#`xwf2Y2nA%|?uvZ2>*l zo@ce|)pVSN6S*+=`8bv}e90~zNNT&2BjY&QK zqaO4FB4M!^6V_7gHpcNV+HrLZ2Y zqp16JF{4)Bv**6`uQVJ8CjXIWG&@y?i;i5I98Z@joq0}JKU<9&k|a9x{^r<3YphsE zgasv%beo4X8NjlQXmEGOc&F4dOrZ%nfnS)+z7lkpASVKOCDB5WAEy)41&Mrl6;U=$ zq$UnjlB{zHM2Ltt$n!Ifc~X!$>E;L#j8DNgKDdymBUOmHI>MYx7A%rjoW^$(S+cP| z|Bg`#tOYXxJ|Q~YzBtCuScw+5w~^iPTV2~8MyAhJ#84_FnP#rgi^r>5MqgkipZR>< zwgut%u%-&$Hy%@z%ufYfErGvM^w3l>ujzl6i$v7qE54dkWFuPc_UFDEbMN-Gu}buO zfhX{ySyBbF9ib#N+TzJDU>Xj+8W%^IJ#mpS-;f@^4rDNSKr~!K)KPfdSJY7`rNNYa zU0lVC{q@Eo|Hm08AUZeCnJy1Ij)Ew9+%XWycARj`%;m;yyWAUXXEL()%vWxjvnPsC zJ^%Ltuj%=OmB6gKg_0KiyNnW1wMuzsf8Gp_mY^ek0v&PJzzPk+OU37IJ5Uiirr>Hh z%sGOr7C@Rk=D**{KA+FuH2JH=XR8htRoMTZsSc!&1x$`*Dwr{H>>JvHX^OrE*^q%i zhcNTSVq*gh!t~CFY{7{fYK$%Hdi_hSSj;rmKc3DRiYV~hDKp{9Im$274A&{- z%NxqXQaLURjXX9zIlC$6q^)KlriAGu2RBV_st@OKdTj2Et!N~~aRB$BP$U$VA`kC? z>e1q6xja+KX38@QV-wko5#7)%K&wJ1X4#5vf~S%x+W;Nokg1=7ueOrKK(i2iqRR{O z9IRl#TG4cY%b^P+Hs0agEKKIB1tI;qsBc~YC1<<(7) zt9IX{Api?n&CyQZHsRQ(tKp-r4ZdQ;&(M;Ov#=cgV%!&vPSJxSPW;Dl#WWN2a5Ju| z;jnEM*FR}$yduakDg0yG9a(?Ob7RPNG}iZNRl^iLPl)Q(zy14i6sp&AMBXr(^gGE| zG%UF;kM0#D;;sBsUu&s~=(-&E(*}J(9~MVk1AS`!xU+6L5G=k59qr<~8^m}rqH>Z# z?e-o=Tl)AfTR7&mwQ!-!8mifWP|#;9g#0ZlbH zE6?;rB*J9HRao$o*;Yso3k0dj5JT_Lny(Qn?2~%1atx9cSx%ne5XR)pmv$0mL6If72vV5>_A-aXkgk^*yx)RIM zL!${h{RMVz{F^qBjZ%Zw#{?h9)t3Dz~W-dR}3>p67KaOAR3SVMMsLunK zci(Ag`^qatJAFF&+`&GBsW2vB9q6~eyxuWwF&wpI&Hh0oCK_Q&)489ee=dp$tkvkg zq7_lX($SxPjege+DSB}|nbVPl1|d8sZBDSP3jN+QpXOe$9?3-U1sC$X5RXu)o~2J8 zbg}j^*vZd<4%I$30v*`OGtA_J!%~@(ku1Zy&npRvq(%WbvHY{R;3oam_tq>Lgs0{; z;-xu~DwdFnNe);dDu4oPmKN=>wP)3Z%qxi-$h?j_XLug@chmgqi8vD3pHJy(BKw^Y z&t3n>w@i!MzCK+t3}oh+1q&hdM?d_`Uvh`afY6~N3PMNLzeYcmw2}<}mahMh`vEWj zD<*%5zseEBsiD3KY;suS1su7s_^>$^B~Gh7-a(O};fGD5NEIOZ?1NjSa8p|0+RQ8> z@qtt3&$k9Z;01tS-Y#t2GB-6j(HUWC`9&Rp4bL3U~y`o2wz~L z)CUN2w8EuQ;}Z)z_D!}%8n@Q#g4XHmz2(sHg9k_5SoASnbA}HO<#V>BVELicW3gI& z3%|Kqvn;w++qrGU4XBY3pdN(}7{&Ys&eI}O%C;@#8`4!g;3^*z^Gpi#j`8`Sy#z+IVi zuX9VdSlTpqS!pZHtB)cBpmt6dU!#2ZM&-Y=ToLrdS zEJQ{&n9dI+_1VscomLQR;%}lbAk7U990;%cWc|yYBxb1?&F6BNWISQT zo9T4GnN3a8WV=-A9?7L3$sh^SdmL(lZzHQd^^|=e(?Jlh$9NMN21KFAFiU4g$Tr5b z3)^o}=+~lk=(loUz<_D4A#{?Y-p29OSup2bpE((?UfV^b?Q~n;A?nwGFb&2e(N|uO zTiCn}*Z*0$|DY^j!y#twA&|mN{!r7?V|u(YadJXCX8pgV!nzJi(@ZA&zE2MP`BLy_hHUle zE3JN616^yfuGjni5@*Q zm9Sq}sVG_QnU8W=$%)q;H=SDR*p}zTpk0%rqx--5)wy!nL>Z&w+w>p8rhgR6eaIi5 zO6m>H$8#uDVzBo=G2_=@=m%EKH`@@kRGU-BVYV5|kTNA>$YU^6#}w_2ta%^B`&K-_ zIlj^A`zamdd3=xfcr;opY#HtWf2CUKu9Ju;jTZ1B(TDVBN3B3REX+=>?XrfR?4rxpB{6w z2-OGzO(uB}C4GitCRp6UMospgfw#u{Se$)^!ckTv2Q7!LF&>72Bs1mybmr0C5k`4q zh&6#UhC*r+`7b7yPZB9=TDJ_7>gCw z|Gc0`vhvN;weZJNU$)1eOebtaYS+%noMBehzhcDWa;Weiw=kfYzmTyG;s0y(ndP*NmmX>2LR z#yOVPaWgYFtsKi1q_W5spt^P=TQE+^4yx{+^cQe~l3lwwbx#Kim-!|a4TIx6&w#jay&1g0O~a#QDlTI5JE;S1Fo%v8nX>L zeiT0YE}xDXx@*yS#}D^|d=Vz_#I?ZFH;ceVKrc$HM7b)Ec&*N>4I6~>YlDEg{GzQn z%EW;23KMTo2Hh6y1K)8^I2Mf?R*yc<&ghc+X)~r5|9dlT+Yi515(Lv=PTHp@iobbj z23{$=5`mZO#+PX!6VsrA1*BW-q3=*ZwJg;4K^TP1>tEcAIibLedog$;O?v*Emjnlw>XyY|gDPXOB+>3buWsQ}+|Fb&vpZ7^B@t?S zBYnv;{~FI|X}1)0JM-I3^GBCkCJPgQ#P`R(-f7f~upsi(#<&))uh0?h%F`DM|@-qJP(~6XgeWqCf>VsBn`q^RXk5ctq^{*n`vIOHbePkI%Qc zFEe?|VeB%-4xJFl8ygz5AMw3A&<$}_-VI}*L2PF*T7uDH=*Vw#6qyPPJ!HV5Gnb8z z+fD4xBLCwYTo0~!j~&70n@a@RDGsCM zKl1RycYpfe6S&G)|7Uu3{hhc6x&BJ}lk3;8_(5$!pQPZB8zT({hm$clNGxknd}*hH zd*CCHGswSjU&S=?Y~ELrRNkPUzQEG)C_(B2m=5zY{9tUt`9klF=$uoKHs`f6XM89U z4r`iKbR0a`L;nBoQNOVVO~gTsjtuVovqd3Wvf& zRaa&t&NQ-`6xGr45(#sZ=f!B$I_@OoSk$bHkEHZ)cvl&rRw;s>83|pRJhDsWY~~B^ z$mx!gFQc>o^fs^mgo}WW@}x!H?{jZ-l;)7eLDwhZd09W6hmu_&G1o@uaSS~wCNZeA(bvY^^`MmEEd}Yrw}opHzB&?KJ&p|PZZTerP3H0=}z~`Wmzs! z?wM;;n|rTt1T?GkU$L6>zDUNo4_> zqVZAF3YliQnDkV5KS>X?0*_$coC6+dp}X`gfp!Ko5TMZ9Hc-*Lk)uUgo7=Kg7(&q2 zEi^FWc^Qdwc-3MX8P6y7P!TcTLDHDHHgOstD3ECfL{`n9%7l>&6_0AA*so8R1bK%0 zT%xt%*duf_F&;?|`?u7Yvwo8pZb0Ywpi9lS0wq5Po(ZJ-rhrg@R{}X^wdY6E4CjLn z;?3q3Ks+^-FK*JEa6ML1weRE&T^rk7$ra}WP^jj_;~H}#X&8-3VM%kf8hs?!&3gGn zE}2esi$zq_MN8XM8R#SAL=jk+MLJ%Vy)Vwij6dn+Dw%vVn)^2n?O#H~k`L!Zf^>kfK1~XcLFJmdBZGVq432-7hkx! z9#eoLEp7s33T; z9x@`lA|2N#s+^1jYK*Grd95p=$tq1h#@{qrQC@6p8q8BjnKH zot;z2U&mrcj-at9g&K8PR^_>bo6Qx=)mbMoHMOtnI5DkCcQ;?3M(2}c*;2}gghJ7F z35iNv(HWmcHB*l5*pj;5p??LvG>;8GUO+zQ&pUFfZwL>dP97bHN#u{Bz;Rr*eE80z z=ugTY!QAz!O&n?M+QaXl?hMoI^AWUyZrv72vPj`A^Z>N^6Nl&w`*j;VAP1ZT#4^wN z+f;(~HlGmNK#0JuLV^!5X}%3$_llan8y>q>z0FTv5i&~ETSkB*V5DzFSxsmJw)Tmw zbTfhVICk{TBSIPv8W>;5rR~=oGq2dETij>9pzNlEh7mNDc5ZSq{4d}YPxgE}5adC+ z6TAe@Z!-@QIEDzODw0fE3ciOcinPj@@OR*HR>ay`8c}J zsOYJzh2Y@&_hO~3=oXBO4ymg9y9G_B)P8xdRtLKNQrwbt-K@2j+gc`FsMztC7zsPc zW0mr)?PN-pxk%XPpoB-zIgtuZI-SY8$>?Osr7zDU)6rtFux-!!PEEIEX-d@^MFo7! zd^o0a>-{Q7c=cSgT){RBED`LNS12S4y_-Nfg%yAegq(SDZVH|pHYx}_Ie(2TtWmLV zX@W|ozW3%E7}i1r7DoC2sM!}~0MW#DRZaicLrje`;)z7B^}{h#wH@60)C70WGwW(n zQ6$Gf$6O-$Gf&s|Tqj^zX^6_3E9L#vbERWPq0cZ6z*|Ib5c#^-=S&$;9e$I)eFd|b zCG-tp7s4Xy6k#XN%Q=7dQFb5h7eFr^NrG_R&|UDMw(z_*LRGW~~1U4t^|(g}YW zsMlx3AJ45e2A*V|0ZaaYmw_pto%!|WY?MBm^-v_8WwTRE>z(BX=i zM81^T*_!(*CYvY(q_8bhk)Ki2?7Ek=;-(ZgRR=;~x}^l}Qfi-}->Vg|jI1^xhxk{E zobqR_5_GL2R*ZQM6syogD_j++JC?XePz1N!IQm#`;!a+)5`q-6<9yJA<#Q*I3N-Ie z-p2q!Ogxj-J@y zBgJ~9^$9q6b;R3eId_tkL~dw?SSV~et#MI83s`$B6m*BMabDA?w2SWa7r;NRQfVul z?F*HkZe~O70%W5Egax1nGidkx3>#t=8@w5OH8MGCwrL^&9K*r}_RK7!Fq|TZf9JeCb5BTKB#X*TV|EwEpKH=JK+f^`EdE0$Q7khoz^zUb+vr6a=n>6eya?l z*nnOGC%8|Jy^I`jU2Tk|Wo7IKj;ac|?4DY+;yq>SKpPt~M~4{Z&ZB1Y=VM|pcd=Wq z4rT}}>SIUrsRBD@P>~n(dO^KTP}C$6m-VvEM-jz|)Q5%zI0(xpbol~sWD`-*maUs)50W1t0XsO?xn&B7}@{zjl z)OcJjKhfP*%w;{+of;_=M@A=EZ)z+ezcU(b@5j1?P=b|dn$~(|TW<ER)X(?OU3E8h(L`jNhV`9@H)(gm<%lz~2|G29qP(sxF8Z5|#5h)y(z=FkRZ ze-Y_=w7RJdHGnTZ0w2VxTTVia>ezH7Vbof4)#}XjmT9QOf@Rkh95nl+)@4gk!w~__ z3hEX1PvTyF+=9?i!efkH8tbhAt<52smy)zxfC^L;F4%3S(1&{uV7)RLcue($}grh}QtW1gf?$DEP!YnO+5;VWQ=GTA&4EqeF_8 zq1f*LeUGTSGi+>f63fK^5=|?;#6!0_NLm#WF{-LtO-jtItaHbg-OcV|C{vrpHMT zu|yy=_~iqbDA!*#KQ>bg5|({85_1Y>1I4_{&rUZ-P;^!TOZaTdsYK&BB2%N^z?`6E|quFRQ z2Fs9&$)eTH<*hV|GP54Gib&1A-BuJo)^DRXa3A*|sgcdT6zCd7X(nHwHR@tNk{j!I zRl&q>K$m$rW>jl}gwh1P)mByA<$Bsb{ zqK3l^hg23Xh^o%Su@*|!ovq4*qU7mV^AkMcrE)?>fxPI%qN?rrHQ0#Fc=YSQWP!HR zlqNTdI5jZJ2n!Zyi42zqI>0n?%*DGJk+=~*en(n`l;;{zEkaOw_k>MzaKElK%0+$z zZw8Y5q}0HuXurfCzX0rt2w4~4K?jKKar|lr0Xl!J`=%i7FRx#a)Kj*LGP+hXn(>P1Fp7=7ioM+GLrFdQIeJY(2jg|pC}7_uA`V{uYtUkp%=v4mKyT?L;>D30U4PH&c$6uo8{g!_AEiHlP98+r^!KEYhuK#wiXt2|l)K2Pq)R_T zBeSa33qv}6SsZ<@tPmUJxtjTf19wDigjkW+ib3OW4pdMgbNBxH5+3Hk;%&c&d9=yx z{yd=9NCJUHm%8vIpf%BsL6C&1p*r%0enFESYq#4lTJwe$OXu>9VbuL)vZ$R43>eZQ zev%SdyTJ_Om>#RXVAIVBP`iVPJAdQ6SUBmtLy#rpSYJC9-aR5A3xWvr4^%^zquY0@ zeEIt}!Vx-ct$&kVreW6*Eyt-Ap8SZ5`YBXkIaE-R?N5H8kwriwT0~X=`2ymv(-gVr z&rco0{F37sd&{pFEfLl;%Zf(JrD8q@hcIrTL=+hy^q``fHHRj@=CzUG3g7A%jRp&e z!qG`V8tzT;%^Qjdm7|$FDo3-qjml9d)EswHqZ3BwQR`LJt0^-wA-g&Zd-8;5o_6G44JhyDfDeSzHS(7fyw0|TWXu3E?H+1Y8!0bpAUuan89hE_m`K-TN<+D9qBb*CLEw_Z zE=qFjJw1J4mj+m^W_}h6=?Z<>u``RO?!vxqA?%=|adzCZ^r&px*7WiBzx!Sh0nIR) zKj3y1#o^C>x}4U-KDWqLn8`AD=Vf4lzXa0-1=j-AU+c;1I6 z(2I#}dfx55btn;6WFGD=6dxGpOseQWO$a6NLCMFENi;hvghYA#;1Q0tQwU zPG%nBe8h9Gzg9>W1N90^f|{Zru2G?Z4rL|w%z$)JkrzZ(`>TUPaB0_%gas~`C^_A@ zD+q_?w!bi`+jc3fV+*}V1abX%-pwqdz*$dFQMGK>8F}P5^pp0&&yHtw8G0xrMo_L? z{mrp_PDBrU$Xb0OpG+u2YRi6UZef|C>AkmYAQ4K*%xZ?#7{nAYS@#`Hrc){8S+W`L%N?7 zNSe{+3ULPeL|h>ee|xuhx*xoe941CSeq$KU7q^-(h{z{?U^tUaR3dRFgIy@nVJ*xd zuIO!_u3!%~&oXAVrRZL4i?rmX#)wulNT1eorEwv65jt(NEhESg}J`E}Rc#}%3LLPrEUuJt#0={S|lp;c1A%TPx z5alMD+-g0M7_w4{{HBHBtB;R(xhQ&!MS*SJz=ZlJ<02lw^^$*%gC8#!{)#E>HotP3lN$h zl+j~&843_7!9hsARE)xB@9XEl{TtlWf#JiM|K!-UeAr~0?rDj5I0H8C{cO{>2;D4RS+$(!HGso0`C2q-Iu zIB9}rd{Bec#EySN+r(s&?R2pTN)g+a^#Rb;?KqqqoOwSx^L@);3+;U$gn_C|u*G3P zC#;aaK0!JmfW=rrEHM7w$T8rYc(L{zCSX9ZuOT;TVkCB+e{Xp4gSBN_$xe2B?TaVe z$kD*9Vhj@SP@hS_BKVK1MD$-_{|1iBnyq5A6E)%yqm0U2mMM=5$fFrC7k;u9mt-Mg z)JqZ5MKnxv4Jofg1Z@5m3Ws76)15*{NleV12gRR5!Sq+|2IicAxBc^+H zp_{BTRgV9=D;iSu_;}O`{XXi|);WV7sOs zFW}p^z7&Tj;(qNLe+fR`>wgjKJs)kOQ)7WubWD#cb+iFJv6n_)k4*65+547-rTbZM z@Xa53|9jv4PVue37~thAs+l|fGibbV^*Hz?}6>31hzj3Vwimt!6c=FGT^}1 z47I3_=Gi^`q9!J5C;@%eZYRtsMeJeYL@3Cc&h2YiA7rvKaqj*EqJr=PriO=I?4czn z#Q zo2eA1K7Z5bFk;B@hY+X|Ma}fwR94ea)^$dUO%65DiY8^#jYOwjw=C0oaYGW-Skg@v zG)*=0Ic^!HscJ+l6vp$3L?+ixCd(Dj%R$PM#YqhAnFqMP7;-S$b#xWBVy>s5LGk}7 z?@XX0yUII%Usb90ecww}rBX>Mm6p=JbgNqGR_~j7U$EP48ynijCdRQDhhSrjVQ|3M zh7jx!W6Uy{G;WZ)F);}^feHH(vn6IpfFz!r91h8NI5`|p&F|h=*aG=NkI#)h7SQ#$0_1>+47ukRLVY@vzM2MAd)680 zWyE;|@{s|cEw5C#$FZ?DUUk>REmtP$L%aK_r;amk4zLRD*FK18Ca1WN)O+~DEjS4n zwK%fPqM{=#S61j~GA23PaLbhAZcq-g|G4_tn!)k@Fa|}Jx7@iSVRze@`0v@V!_?o| z*8vc`wIvap9KF6Az>gTr4e2)=s{MwTfKTAnc3eEJp6ot&=m;KtW;&`Tz_=r(G5M4jXAEl`c-@9cJH$6Z^qu6gT3gY^XXw0uR3a{Qc9!< z?G54ZtR`tb=ACV$Qb?0GU8^}a62&&}+c}B**>`NS`8PK_-E{FwavheF zQ=0CHt#mt*ai~BSegTc7j)}ofl1V5Wajqd08fMZW%?uucG(qcHK*g9UuZhSgM}yI3 z^9H-R)~ww=cBA&;QX*5%mAWQ^9y}_8v7jdb#=boqX3C2K%bKyfz)dL>qLX+^Ts6Jp zDb3~aFSIS8vp#3&a4})lDUQ05Ymj` zqS-dA&CMp$GD_=LJ~K}I?!xt7Hkt2Qc;jqqmPOUh-S6qn*t$a-29iz}e2~5~t6eV^ zkhwy2+*cdevN!xuoK%vz_R(X=kVIi5qJ_}D_m&e$uZ%?I`TrMSywA(NoB?b8pP@sXSzSjMa4>!h zG1wXNGCd4zkrWHPLZU$!<%OQr+(ndCs&59%21^f4=eqFFq|2B2VboMpRFhiE;!jOK zWatmEXaFHRdMuR5mYv&5F}qXqqCbg6^3llE!Az#t@loxYNxX5T+BA}#3ndGMOsI1- zo6Ho|;fQ{)mNE~QJ;7hN4OIofMntw^Q<+9PM&GetV_##lhKM@fTmK=xx zw}wnNPbC|gBqcHH-BVvP728jBWYR(YGO5mX_5yyiX4+2z{%mWpDC3m_fo!JOk;(c4 zgGHVr#2Mp@nunEfUVjd3jwK?|?DcU{k3&GqD(9g{wum(O&j!tN=ujrDU$4ESu5ZK& zNQI#EIyl>(RIEV#23WVrVo^D+F7y#WE4uY4u*$O*BS~4KqjZ# zVl@$29JjlSPB)k_qcNHLC!%?b&*RgR#8*=HueA$M31C5FA48@Mu(-70>4^qfnm;A? zAX;jGx2!t)P`PQ?UMe8;RNXThwiq{QV>sK0jf6-k?Mc-u63Oj5cC`p|IAU~41F*Wq zHlh-xmo6){o=Kx94-Tza+v1$Upxzgdw%rL$XUSL}v3CXiZ zu-`Nv(65j(=1|_DMWR-p58EGsdx0-xSwWSCJkbS`-6nljmL5hlui5*O4yWOr?nhe8 zj>KPC-GsIqj1EWk&iB3Bu&v;6F8n!wVysbQFciMc+I00`*oX1hZvNjfkJAJoi`~)o zorM>);xAl4{mVEi{;XC^J^1Mu!wWyrve!XtzqBnIK8tO6gLtiK^-EZn=fzC5Nw7M7 zNgK1i?pZf&uvBN(8>i~5i?P0XyK_+&m{w_izPBzPt=tOzv!|ZAhH;ia7D^xsr5dsj z@S6@Y7sL`~5jPQkRX6%Lq?Q?6q+X+3W3xDy-9+*@3!fv0b2FoDW8-6eE(b{z3{JNv z+gbI*9iX9EJk!aMa>`WhEq8!kX-z!t4;9Ez*1LkITn-L+$2%dD-P?+t%qsd8vA&=KZ&U4K=SOht|<-HlzJ zC7>pXVtJS!ayzOpxSJw^XCkcmfQ?#8JnH$SrpYa1VnvQoQ3VXXyAOCK++R+9OO-ss) zjb8Z>KWls)u>@<{Ch|Y!w`8n~7v;<W*ioumZj?0 z4MH6u+7%-Zb~#3E|LM*?L%A8B#UvdA&yLG8L;;gQzOF^FcMc8qluPDt)0nGQGaLaS zJ!7Z<>dK%R2}e|O7f13+*`b@rNaJ(3;GP{BN&AUc!`__=H>GmmSF!3qZAGp&C1Iy` z$kiW#7*a(m<1wUofh}yNPckywwxfIZ%(dVXQj*DF!Xe4o7l(voT5_c#y`v;+k}F_L zR7ChF3Z}@!qUHddxQmxY0_ya}X`FJ$uH&7F_z>2Ha9e98727a4QXo54*uFmzjmFUv zz3#T4r^Vw*Wb9^($!767XM3nucaCoeX5F!czg-We8mUfD zsTz?^@U8zBvBuyrT#tQqOdM2g^GYZ#?AMc-CH-3Ed>U{p{(fyb=(%D=VVAcQ-jX9DYgaWh? z`rgl@q{<_!ZmdhHp+w%msYa7?Y+?!%zTFsA5~h>TnqX*{*|jXETc_qGEDRXd_?j8$ zBdvO}QyL10{P{McQ@Q7pugKDB6gEul&McY^)j$0qgLG_F{gX?|eazUs(=$frdXyb3 z3h0%Q5QR<`rcT|5RZf(^2NL)sji%CwN|!v>bxIvmpf16?uer;4|NRzg*oo~<+ZJ%! z{9&v89q%yRcgs7RZZj@ZU(ZBC_S<@g-AJnjpQn4_JF#%kuy>(9JUY;u2e%6VvRDk= zfhFK=8yJ|zg9uhf{-!uU5Xq$Xg+n)_9l@~IY5|nZPUXdHZ9=@y@Lgub#`=Y&WV25L zL!IReN935rNy?X>-L>nB#t-my1iQ|&tV2(b&-PKFaj%SZusgfu%0;+k z5Tkt)_gJrqy+nV^iyJXBRvjb@n*1G38NF4uj}SX{%^jmVA4OQ|uR8C4(_L@8^LO9y zI_tf+q6fc8+lybda_w-TUEx#${Xqm?@j12j_U$%)p!4W_ulhnbr*8_ zxhllS2P`?IoL*En%U-PeuAQ~%oE3>UrgN9NSywh`L)Z!Raj(Yexju*KmYq6-mt#e0 zpLv3syVS*XHTD+`511`@(oT01CK(8{=gHLSaRHr93{DUa=6qZ0aG@<2xOQhUUoUO&& z;?)vY z@Tb~Rp&*EU8iF(_ae$k^$CBK(_BLK1)s{?e+F2rjg%{sye?mfhdi=Oo11*k0h@1%6 zXJXiA9wYiAD>_v_fxpY}VCfGuA5Eja)JG$;iqvDc{7k&cP4+CCKc6JD=7K_w;js$~ zF0!AGzJ=?zH_F-xQL#vRt(qdVES7-I&2(gNB1AV7$d`)n=))kXZm{B zshsdOY0ev%J?EW!uZB6FuC^L(z;(Rpl({m7VbNAKr&hYVjP9!u;FX}*5GC|EP|bs? zr^=8h`EQXUDXG1=1C9(w2I-ac`{Wrz%DckGz1_Vz}j-Q7EPzPhcW zwVkX+;Ye3$*RI!03=9;3d<%u!3xflrAJ4kBMc)Y(- zO%L|AkR&RY&1c`_a3qtZgP=Ifr;>w1)#}XQJ#&?QtJ#~W43Lcc=-k{u1y8fy{-KeH z@qxk4u4vS1b!W%iZ_6Ony@`S?(FE_Vh%e^y$t{-tA0hS7$7K2p%Q0 z2f)wxBx|;{4OC8eClQROm4yw^7Gw?8>J`wDs;+F0p-qbei7N6kR^1bgr$$JRt^}Vk zIjcTBq>TZiuCpO$1_tB~l5~o@AOs2LkW^g&k3KmuKGxFbgl!FIeFXi3q$NWw@gbGE zLAf41E85B#faX$$;<}(Or(Pu$wv#(|c!W4U%=`Se8Sd|lqvRf~*?AYN^X}*v?b&Xse zEzR>8nL22k#aD&(FrT%^`K&PD%Z9Sra|g-U@%RZXe?aJ127bMe_H_ zJFHpwcUH&O-*y5Y_y%vulXv5w8OVQ%PdyXiFk?K=&-Z@tqvJbU71 z(=8~?Q36yw;oPf?V(kX0v-S`?f z(3xsSyYW`Zg6yy$|C?YEhNoY3G!4Q_OH{?u1?lJ8uIBQi3xAd&e3b<21tJ2kep%FP;BoK^t$K1NLhX=r0zSuR;a*lY}#DYx4zixv6}3k-!|%wx8-_+4|I zrS4)Vm`;y>&hN(7u`8SLdDi3&^XW92k$h_=+uEAWt{oa^%_LL(m6@5@>FKGy!DO-$ zi}v)4j17*Bjl!p5iCDSJEgPy&-`VK2fLxjT*4%ng~ol+9yJI(sIjJgHQsE13x3 zgX8qK`vSdt?_~TTfX){1Ar2AS&@^9SEsbtID_;fuY^~s+F7imlTmXH zG!UIB38vF|@*_wA-`3T9556Od*klE$xCFc~U_p)B-SNkjz%qZolU9r7*3?R9RY!kFZ~SOjBOp z4y!fZ1)!TH5M}95ap&R*uytit18;_uav*-hPh{Uq&_ufhqCqbr{7c= zhHg0cu6G@Lc)@&dph7YxAw{Rp8y^xyxR=WBR6Q2~d>$QplLBi=3@%Tr8crOdo5AqBRgFrs2 zxd)_PbHx~cb;qlfK>g+FPFgEQ{4+SETNhxBvhOffpEbT0iDp0?Rdl(Swpa9_?Nwt{ z_8lCkE{t0TMSA-Nj3AJ^%l%TCkgL7r!q^%0(XuRcZd513N_4l{3z7gDGH~q&wFT{6 zhA-fH5vxaLo)fV@F9_zAlSw^pKUQC8f0{)pVf77QCO5uxi2B5RM?t1gR^)noo2<49 zX(NQK&ILxB03^(HkhP~@4ji%ZzWYA)DK)-{*E9SjR{CM_KD~`D9N@4ZhwWidlL6&Hu!VjtY`W}S8y ziHw^UgTP=fc5rYzmlYqUlcXOvW|L)D8`4R#l9;(VIhGwd_aVK{FFS(ngR;NZq?*bL z?$19MK8Hu=wmQl@%WEJL@e7p_6u!VAZ32j&E<$O*{nQ``=DJ?Yb#&ND@I$M?!x}$hP)o>;ygLvcO;144({#vaRiV7jGv`xkqQY;6sBFFGTpm&UEef`8-#zz<3 z&F#{h#p0HuqJX2edaOOwLv!=*s=G0f%1(;TtW5 z?*58t%PxM&##3EoN5`b!K`wANmN1jKvwOJH^lL9`&;Wsv31rtiK62Sgi9p2#SqShgeI1Q8+(z+tN>aj=pA{d_@ zBRcefA#~)$5p6)rr(P=fyv`@P9Wg-l@u)LALHyrX?-N&b-v73@Eo>jxo*C1~6J$?n zho(cG!Y|__;4m8At(kkbZeB;EUDfcX3#SZ!fp<^s*R_|77U&Vh!(~GqLEj~*;SS(E zF3hz3yhtz$R$YbmqH#c*rq+4ZK!NE(qp$#W0IbmW+gqv)_wbp+qEN_*m4N+*~LMO09y3G9*Fz&Vav2r!bl`D%|*?YQ% z=slY@8#M5D6b(PUb(=xkr|qp1bPfzP`r{VaHxF^AzNe39vnVBW*bvgc>p?jZ=St_p zXLX}X62MDuH>plgih86Nbw14xa%P^09K@PNhy10VtY3DQBiZUA8J1Hs#YtyLlzu!| zN+mI+dLl#VOc%IPK3qq<&I0*kz)rIIy}r?8(qdgNd+V-2>@L(zrXXtjUQ z%rjGK@;SY)=iqT8wK45kZ1U4&(%!Gj0yFewBdJX^RNadHGU*!nI~ic=2JuD=4v$Ts zYsw?k&qsG|tP2NFSB?QeB4gSkQl?eh(gmVVIax{mCb=K~&gza)DzxjfALbRQdX`LF z89$nQ{nu!XKx?^ZmmU09R%@UIXDd5tVI7vWB!2B0C7VeqVVMdC0w%kQ9!S16Ad&&0 zHrh>4$?vyYJx!@&3GZtU_}wPBpryrTGh0SyMhop|J-xZ?;0(~>KD@syUd)WN6b@S! zPdOF{`@`++s51&L;U!O+L!ZX@m;KYDEssL)dRy4-Afc_> z<+i%y{551%VsWD{@q1Nuw ztZ?^+dT@n9%Z^@$`-}CE2DOLGWxI+Iru2#Dak^a|$?b6k~Pmgx~!*q*_sBoLTLANf$ka|gTc&4Hxn|}Pu8O`N!$)?tSM>Iiium2`4C)d z;DM_lYuy^A&(h~J^Dd9RlNU4wFNe7IT-aXByu3)`k0}~UJlQ9jXpBxwpAnoLWwb%P zu&M13xoTe{@nD6R*dQgHPHns1k!3dOs(szgt(!N_n740OZLgkpmT1Fy=MW3i2HmUn zo!hqpFl`1IufOKTgDeJ=Ml-hinVOhX0>50x>ZisPaoAR zO_nn7n+okli;cA4daOHwDk3Y(ougQWKglr;Y}4>yac;+bi0Nn-P3zo2C}rR2!>=+} z@zu4}mxQ`547(?Z!lf7_!P}Ihz7qGPKZtY;l9DO^8X-xfn1RDIMI_a&T~eoL9s$Z- zyK3Rrz^^(zu5Y$Qd}fouf6ZS4HZ)`y?hJKK#S<}?*IG(b*m{X>@Og0+A%B3+-#X&O z7j~qh9FMwUzr8PQb9;TDEf{uv{0Y+A6=HFaPssjcGPe+;l>76~b_N1QYwY<$6_zAK z_n2^j5C13jHCHx0q0J2^)8y5*{yJc4ae7-@w|tqPb0xn4p3)~h+>u4Gq4w4aDrfc$ zb&acBIua*fhXYrgsZ7p@dn~I@$?;)d*S{L}d<|{1sFqTg+&BR?g0?#}pcj?WXjjd;A)XZc-?EzqiGKoS>Lzk zoMEqL)mgT^q$u08X@B17aQCyOisbymT?B{X+~WnQ&gu&!9AU4270I`E%pKUVqp#1h z3*AC7XtTSE{&1*2?{N{t%~+B8?|YGD2U!5Q63;w@YX?Bkt1IQOl=U)uud&;^Q!i zRZ(t6Q4SpO)lp74@yn@H7j{=dyYDuOyvrwDv)t>J@kDx`dx$IDLCzq6oNyo*^q|@nU=rk+#H?Rtq-_wXcfl~9) zJzG~rFS`sB+^M-XPOX+&OC^W%iEk?*UZ;+9QYZSumR9Q_i!W9>Gzt{<$Ul)cPO z9`CF)6Z69PvSD$Za-eo7FW_Yw$w!*G+9eZdpJ?V}%QBeXyU4{pBX#*_>)XiAa&CRA zHYu=*c)1a*r(B*dT5HAFJEWqbIp?x;-BDBSISH2K%z6$-x@5g4n%U6u>fOwOS`yMn zL$CY)6+RJ{tZ+(aAHQd z02a6AY$Y&}dj~_{1V;u2jm}}5-wDl2RAkHrz^=l@IxXAeVorWnhKtIDLdJI8#X|Y= zHhc1_paQoxdCj)1z0-yEJ^S9W@GVruN009P73u8THkZ2negajJ`L=G5pkDuakTpCnj@wH{M>E7keeGRE}=r?d0fQzZeUQ z$0QYGwo>Lll6RgrOLx_g>zvjNg$>im%eYD1tKvdAyIBs%=t&`}pt#P93e%g;WiFA% zTcT^7HzxX``Mp1UF|EvUlYifHe$5P4P@>9ZPH^U%Ry6WE>pia#6Q{KC7t0DhA78(n zsi?Y|+~7=3>*eG$vsw1IS1eEX620?fBn|&c^F()98N{`>EY5)E(0{jhuaZQUP5-KW zc8lza@lCrW6If+4>{iXN?UikYrRwd=XowFq&%Bq}5Y1EaVp}|<#=>Ute$^KC{ANkv zX!eqWfewxRUWi#b^eWrJDK^Y*HWFyzo*~fUl^hkiZ`Wls!?Z3BTxJ7EL7`*wrbXJ{ zrf7dmJhswtu;D7%N@C{Ns%dW*b9>}Hj>-@hvEu-2{T1X&7n%$%qea>-HdS0+JH7Ax z6Nnitr#^q&tlDj_xUL?byMj@Jn3LUzn6sN4HM$Wi_Ay1mz3H$!lU<{GRrYhOBTjVp8x1m(pSjk<>~(gv^5vI6nS()FAB; zeM{UeF}Fp_Hy@gM=#i;M-m~_h_fBcsA71;&!_|i$UiZ}-hTE>RxiF}oa zwt=q^CDHa(1CtU=g3Dw)ajCQDiJUK-T?Mo&t!X2=fM9kDIPncFtRCnj*Mr6e*ib%a znA~~e_UZNIo_O4CaajE6o`VCM=5BoT++24V$gS+`&i>mMekwsDtyV|4qkSkI_FEj* z;BU1okg*-b%0a_@J&9yr|GMcd{ln3iFWuAj<{SEZdw1`?vAfurn;p6B;|oW}y0d+S zuAXwL+M2OhTP(KVf>-;<<~O`)`|%MTdi~X60PX+UCgz~W&vxpDev!E6s-zNXf`YKtdbX@8To{!B>#`EnXUJF*~?Zy zI*{Omdvx99kZ!4vv;yKen}woP6R%kuk~xO;KKIc7n=uVf*A*J8jA^*5uH(>YbTc&C zjIHk`oyU!XBwikcpJ(Z+xSRLkJW4#6(u_DgDjSEIXXOe9cQWT7 zo_PeiRc7SX1b^FHGT*Q?2kR+2o8z#+sm7cp8Czq{E!AOlhb)82v9TTl?lF1?LHIWL zgYDXmkjrBa;3{j#94}?YiybrV!(){#EiD0m=fQzoG~soV3(?_eZ7Ud_jE2Mg3wujm zzl$soCR2;uMfwo8drD?E36#65hkD4-SBzkGG)4a=4+2_d9q71GhuMbfVjxIO$stA7 zbU@6+;rh&KlevIM)o7K&*9ii*FFXn2_pnEeW|2%Iuyo7##)gjV49>=66+UYZmT;A8 zb$c+GJd}<|nSMh9K2>eyeb{V4_aKMpCfx?1ii;%E4P_SdEZ&8V=wD3qx(NE~E<{2O z8+byNNFd~K6%%CMiN`|WQcra^L4>isSTvYhQ!bNy{-uRG`g|4}PL{KCU8Qxyu|(eI z>+af}OeK9ehslHsd@|YFGdSAO5eG@Vqhp|y&jv}5LXooB(^Nqb|_%d8>uee4$ zpy))@wwy7eUV^Htb~m1y{Dp0N@s7_JX0_~~p>4}nV{}8FL$;lAC`Na8!vo{{K@jLa zy=S{&cioL!Npx{3;4R{Zo!PEgMzu(9P4_#JURc%KV3q5=-y?wAF{_l`f>Nx>D_S_eFJASF|;!&#%jBMVF<|agV9? zeYN;q@$SnTfzfLjf%DPZPONMk(!iux0SZTxf9#;+CsMSS|Q+PG?am7E>L9Y>IGoY&r95ZAnhv3dP1O=G%hKk>%f zZ+rdC<~wd_Qf6IP)|q$iqsPoU!B=T&{(aIX_l=wW zv1T~E6?s>}(d&s1Pq*?_56x!U&_X;WY=G1v{bYnT|VXm^ppep5P~Qsx%Y4G_nU4DPE;CDM)K#bC~Y*w3no6nn%c|9#A?S@@p4{OL&z~y&E10i?v zV8v@QwWLz(+|zzIu%(!=WLwh?(AjzhZ18>VfI@^3S1VCR$zEo%{ z04CoCz+PB_fMyDgWZ6$H#GJ{CC^jt`<^x;yJ?F@K0GAc9Xk(v-p4- zH|}H_M@Iw6LW$@wK}dh3*k2)MphYCd_$4|han*hPEbsvQFgU~y(Tt4ni?5Xtey7Vo zpVp(c)#5M$h3jB+N(+|JoMYTV{Z?M2zC-~yL)ujf#vomQwNJ4$-*mb`!9ZXt&k$$No_0=Q6j1k%QmpH~v1T8i)nYkkA&T`jOPk&o9nMkI4bAEeE z%Wpg&9cEmiuy?B5^FYi?t_(5-8t(Uamgu;(*SEH2OUZygRZbQB!Cc#jJLmN~T$NNT znXQz&6A6+hw?w(#D-%~4eU|ZUU0nwk7kZ4Fb7;S{kqu?Fw6EHvONNL~ZkS81mVmr}H^2uC5(sFD!_!s%d_9VS$@D8hoFh;nOUb9|EGaf$!tc+?oOK9xV!l zDLg-vhW?G&6%9Se2WKZ&Vex2s5_HAhgPxa)6E>3pK)WkqcG!P+k-suUNE;aMo>lLj6MJ=uaOt~O zZKNTAXU1|(^1-EQkfR2v3)kQrMz^#M9ZNJ)=dHsM{$!}VKpo14=b(RF%$=_eWJ8WG zoBmZhV0&?sC8k9$T!T8JSo-FDbp~+Wch>D`^>=-YcP)T;zx%vz8_n^y^U(_M%FF08 z3{T8&J({Z>aRIaT%R9`a*T75b(RJYgbiS%-+Jy&*!bghL{vCY#d9bB6)@?Tq*El$D zatR@QeHkKFTfQ<2e0{2qURJfQpRVE|VV>-3T1`wdWE^L;=aFIhn7*7)GLo=#X=E#B zu>r0+NgS2sX0T$9k>OlrKxE`Z8jFF3Ai5Y~Mz*b@MG8iUt=Jn>%%8qK(D5<#g#ji` zPm9f9jM@z8@k(cBD^TjG)IeqZYbGcCp;W5-X#e5%Qb)0O>-g<`&zM_?{c9%!GJk%5 zF5?CE+pyLbvbactoHca;I+H7v`>SIU>D;c}AKEZE)ltm%r+R{JXUx?zo>}s0tE}gc2jABCt#iLH~?Lvvg?1MKUY=(wzD#r-8LnF}D%V^m!pYFzVq4 za3^V2@C{4UP?hYwexNRCTEJYF0_g=^gtxeysb7ZN&dz>JS^fVSaXL+z^yXrFA@*F* zZE>x=sP^pkg%`-~V~vm(HKO@Pjo|$Jrk~hLpN}-^Lmlm*NXMp#$!K#uaDL4gc-l(F zk$v>b;8Dvcy*gD}fgaOHvk*;7s(QR27I$=Yo51XddfFsgSD|^I#wjOfO1GWut7lqd zN{X`_{!COd#BiOeNbn?{v0#YRd#fisSi!x$qrK49>aYO)HbN3_$W=Gs46D>jy38)e zwob*Afj+7)g=Ie-T4K)xxpI-@(N;psk>;sug|u8Ur^z*^ z?2pf>{y5P<6u1N}>tTBworo>@YcM$A2FRD@_a%K9U@j?cPmC$nPVr+9|6n^LV zStm9Ho#u}zn#%~vyO%}vC1H~*CVErMLCy=(KR$S(K14gTO5|EzOLbs3DcO? zt=;g?E!Ub8*bpCEm5mL2lKe&GL)35&+lri{i@?I|F@+zjYXer) zC8Z=|m)LGzY;`em_VltEX~fN4qDRh0*Mi7AAKd}!<15r7nLzr0kTy@@a9EuVpqiNS^=u@o0=Jd2bu+uS{l7@EhM3tK;OIcb_wrR=WMez-92*(vFPZJEr(0Mc>u^u{l6uiJtm{+jPQ3ufOR-xw z0u%eqMMbU9iI&D04#n1VcZb8FP*-X6X#THrcDo5{WL)#@a5@6MNWa&!pI;r%1Ouh_ z<{TDVJE1yj$A(98`5jt}^dI)zZ)aMaZXli_hJxMibGrry_dVlvXIflptjoUk7=2Af znBAbuL$bHcv@~}&XKui7wztg_Q6@*biYQxNZ)5Z}g^&d6_M#KKb*=)(?VE@N*%|t>`B^*iH#y+a>66;EsWR=6nN= z(|Az3HnBa<_DdlCIPQ6bYC}84 z_io;!%sD;Wv-dw4MF-jVth^+|z|$nDd|Zg37fFu%Ss_Mv#>kgQ{O4ukxufilS=qRL z{A)nwaor^KSdLi!S z^X`8W;-23V;w@Zz@7+SY_5DKJ$M&{KA@09Zh_}C8h<9+!e|=ntcRnD*1CI&u2R!@T z-0Q)w3Gp7D^KejzM_Pq=FXzASheEvnCLuoH6yj0t^BCuRko`Xy6yotGh4>Kf^2CEe zeE3!&p5$I1;ocwP`j2z`Pr1i`=h(@;LOc~^YepRcfQMFC%GL*!e_L4BOrHn6%yNp$EEb{!Gk-w?mUlhT{KF1S0f0}px7d|tF z^~AeF9rk6NV_fUko)r;3Bl45uvMvd}d$kbTw%XsYACmjf#>>8}r#wH%mgSlh?M!#km+6+MBZrF;kI2F)e?d)c6OZGrD*LpNFHwuu4u zLwwG%?c#SyH@9{bw4wFcrw+AWvLDgjMjdD?o*OcJO}?|b=K$w~w3n#&OXP@sjQh}* z@)?GI<$cTQ9ue6l+V6-Sbu5Er`TgXplE?m*w)p{d{tfl|Dfc{9`+*@z{r{f2!%(#? zs@-FTU+~;t)PBvUZ0`}?!G6At_cU?5SGBQh^ZKURShjP0;~X$R8tF@o+DjJ}usJt3YDzj)bE8i!-w7asLr2`kXs>s-=`TWiA~mcYm$i?ej;*%H3N}t=n~QGLhxj)ACW4 z_EY+QQ7U)mTThAnbg%r_docUM#BUBoCQli=PKlWwwNviyl_#9i^3(bc+KhfGXZ$ch zk*{A-?&izC(XT$E)ePbf?mJaYoyw(8*)ykFNq|9*HGEdI5Z3(?^b!k<4*AJgsIZ|R zl9fB(-}=3~_J~%|nu~G$*O37}Ca^H`2Zd~F(V)E@Fe9t6Pg14!TFRSL`8rCQu+{zt zrMvbAlx?-|P_D223FU^`%f0rufYVYI7zS^fK_cLB>;y~?L%7eAfP@b&)T)5Po zT|(a3qxL;&KgXTFA#$Rl_I=7q?R%6%wU1H`*S<|T!udHds><=&+u1)*dlTis+Pf*` z+vd2>dnixVeo8sdRe3U2{2gVId*(UkS?tsK+7Bt)RasPJC+8GISM7Pq?%Fpg%UHAv zqDPgz>YRbv-*RN0JGXP6uOO7s;sE;BxM)pe37!P z_Scjhym=Sr$g-l!1Kg(z`n*7Sk|QPB@hgi$5@ zq#Ui?PdQ$D9pwSuq0Bkoqnxk(6Zz%j`}9zkFHy>;_o%1$sHgX+JNNSb@IK!E2b6BL z@1bRTdH+vPCRN!6wR^e4k0|?8SyAOCRi32mqYuckU6n;uc2bu<`oMQ6yJ@LD`oIq< zdsNx0&Kcl|eZ1|@h}U${-}-4&S?1{TIHW?!e(3N%Mxs1tfTw?-vZBhHYNyyg$^Ia3 z`vPT>dk)fzOjo?-t{^$sV=T0hD=f0q`kUZ5;Nu~FXnzf-QSJxRF%el^NFKS8;Po}=x?LE?HS5_>YM}W*MsW#5!z@}z1tYi`T<&ileHg%&S8T}$9#MAe2{99P%B@K^4jC-+T&dNEppo1c>fc;&ub~=JD=cvTKtt_uS0ewzz=g(6qm*)neN@%uQB{{msmt3rB4^k~smr@5WnCUs zGwh@E8F>de!=8k~|3oP{>Pb<6@10b9^`ww|^`ww|^`zpfCly~ksrc$i#aB;)=Pl3a zhZmh>PJEtnSlvPL&v|I|Jf-A#^UzA}OO8iEB{&|rj#ZiC+IedCZA!`a=Bf2RQ%cSc z&YvnJ_akkP+TY5X&qK){Q|?hu*$Y3JS6p!(TFJXfuJ|nV`W~h1rC(?NamoaD{yIl~ zE|AW->Us8`s|kcuwFy!GM53z8Siiv&F^J4ArO@WT@2evmeaAm$wp{I*{NKo9tVWjp T)!tky7+*wA1H^|--Q)iNX_HIK literal 0 HcmV?d00001 diff --git a/tests/test-data/fonts/Cardiff.ttf b/tests/test-data/fonts/Cardiff.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4e8fea609c6b15e512542d46f8d7f53dcbb32c99 GIT binary patch literal 67984 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 h`ue*swe Date: Wed, 22 Apr 2026 13:22:20 +0100 Subject: [PATCH 123/204] Exports: Improved dompdf font loading permission errors --- app/Exports/PdfGenerator.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/Exports/PdfGenerator.php b/app/Exports/PdfGenerator.php index 10f0624cfb3..5506fe74065 100644 --- a/app/Exports/PdfGenerator.php +++ b/app/Exports/PdfGenerator.php @@ -65,7 +65,12 @@ protected function renderUsingDomPdf(string $html): string $fontMetrics = $domPdf->getFontMetrics(); $userFontfamilies = $this->getUserDomPdfFontFamilies(); foreach ($userFontfamilies as $fontFamily => $fonts) { - $fontMetrics->setFontFamily($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)); @@ -92,7 +97,11 @@ protected function getUserDomPdfFontFamilies(): array if (!file_exists($expectedUfm)) { $font = Font::load($fontFile); $font->parse(); - $font->saveAdobeFontMetrics($expectedUfm); + 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); From 74aa897626e649cfa17b0c07c5fc1b985624e20a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 24 Apr 2026 23:16:44 +0100 Subject: [PATCH 124/204] Readme: Updated netways sponsor link --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index d3a408ad02c..340545dccaf 100644 --- a/readme.md +++ b/readme.md @@ -72,7 +72,7 @@ Big thanks to these companies for supporting the project. Stellar Hosted - + NETWAYS Web Services From a37f903dc7e48da434947480917829af571721ab Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 27 Apr 2026 12:07:43 +0100 Subject: [PATCH 125/204] CI: Migrated workflows to forgejo --- .forgejo/FUNDING.yml | 4 ++ .../workflows/analyse-php.yml | 17 ++++++--- {.github => .forgejo}/workflows/lint-js.yml | 7 +++- {.github => .forgejo}/workflows/lint-php.yml | 11 ++++-- {.github => .forgejo}/workflows/test-js.yml | 7 +++- .../workflows/test-migrations.yml | 38 +++++++++++-------- {.github => .forgejo}/workflows/test-php.yml | 36 ++++++++++-------- 7 files changed, 76 insertions(+), 44 deletions(-) create mode 100644 .forgejo/FUNDING.yml rename {.github => .forgejo}/workflows/analyse-php.yml (64%) rename {.github => .forgejo}/workflows/lint-js.yml (69%) rename {.github => .forgejo}/workflows/lint-php.yml (57%) rename {.github => .forgejo}/workflows/test-js.yml (74%) rename {.github => .forgejo}/workflows/test-migrations.yml (65%) rename {.github => .forgejo}/workflows/test-php.yml (64%) diff --git a/.forgejo/FUNDING.yml b/.forgejo/FUNDING.yml new file mode 100644 index 00000000000..5c50c3f691c --- /dev/null +++ b/.forgejo/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: [ssddanbrown] +ko_fi: ssddanbrown diff --git a/.github/workflows/analyse-php.yml b/.forgejo/workflows/analyse-php.yml similarity index 64% rename from .github/workflows/analyse-php.yml rename to .forgejo/workflows/analyse-php.yml index 647835aeb2f..1214c39fbf5 100644 --- a/.github/workflows/analyse-php.yml +++ b/.forgejo/workflows/analyse-php.yml @@ -1,6 +1,7 @@ name: analyse-php on: + workflow_dispatch: push: paths: - '**.php' @@ -11,14 +12,16 @@ on: jobs: build: if: ${{ github.ref != 'refs/heads/l10n_development' }} - runs-on: ubuntu-24.04 + runs-on: docker + container: + image: node:24-bullseye steps: - - uses: actions/checkout@v4 + - uses: https://code.forgejo.org/actions/checkout@v6 - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: https://github.com/shivammathur/setup-php@v2 with: - php-version: 8.3 + php-version: 8.5 extensions: gd, mbstring, json, curl, xml, mysql, ldap - name: Get Composer Cache Directory @@ -27,14 +30,16 @@ jobs: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer packages - uses: actions/cache@v4 + uses: https://code.forgejo.org/actions/cache@v5 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-8.3 + key: ${{ runner.os }}-composer-8.5 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 diff --git a/.github/workflows/lint-js.yml b/.forgejo/workflows/lint-js.yml similarity index 69% rename from .github/workflows/lint-js.yml rename to .forgejo/workflows/lint-js.yml index 9aceea2a26c..cef1d054074 100644 --- a/.github/workflows/lint-js.yml +++ b/.forgejo/workflows/lint-js.yml @@ -1,6 +1,7 @@ name: lint-js on: + workflow_dispatch: push: paths: - '**.js' @@ -13,9 +14,11 @@ on: jobs: build: if: ${{ github.ref != 'refs/heads/l10n_development' }} - runs-on: ubuntu-24.04 + runs-on: docker + container: + image: node:24-bullseye steps: - - uses: actions/checkout@v4 + - uses: https://code.forgejo.org/actions/checkout@v6 - name: Install NPM deps run: npm ci diff --git a/.github/workflows/lint-php.yml b/.forgejo/workflows/lint-php.yml similarity index 57% rename from .github/workflows/lint-php.yml rename to .forgejo/workflows/lint-php.yml index cb9dedcb25a..abebcc5eca2 100644 --- a/.github/workflows/lint-php.yml +++ b/.forgejo/workflows/lint-php.yml @@ -1,6 +1,7 @@ name: lint-php on: + workflow_dispatch: push: paths: - '**.php' @@ -11,14 +12,16 @@ on: jobs: build: if: ${{ github.ref != 'refs/heads/l10n_development' }} - runs-on: ubuntu-24.04 + runs-on: docker + container: + image: node:24-bullseye steps: - - uses: actions/checkout@v4 + - uses: https://code.forgejo.org/actions/checkout@v6 - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: https://github.com/shivammathur/setup-php@v2 with: - php-version: 8.3 + php-version: 8.5 tools: phpcs - name: Run formatting check diff --git a/.github/workflows/test-js.yml b/.forgejo/workflows/test-js.yml similarity index 74% rename from .github/workflows/test-js.yml rename to .forgejo/workflows/test-js.yml index 379f1ebfaa7..6fa21ee2714 100644 --- a/.github/workflows/test-js.yml +++ b/.forgejo/workflows/test-js.yml @@ -1,6 +1,7 @@ name: test-js on: + workflow_dispatch: push: paths: - '**.js' @@ -15,9 +16,11 @@ on: jobs: build: if: ${{ github.ref != 'refs/heads/l10n_development' }} - runs-on: ubuntu-24.04 + runs-on: docker + container: + image: node:24-bullseye steps: - - uses: actions/checkout@v6 + - uses: https://code.forgejo.org/actions/checkout@v6 - name: Install NPM deps run: npm ci diff --git a/.github/workflows/test-migrations.yml b/.forgejo/workflows/test-migrations.yml similarity index 65% rename from .github/workflows/test-migrations.yml rename to .forgejo/workflows/test-migrations.yml index 80075c3f7f6..7348ff2b335 100644 --- a/.github/workflows/test-migrations.yml +++ b/.forgejo/workflows/test-migrations.yml @@ -1,6 +1,7 @@ name: test-migrations on: + workflow_dispatch: push: paths: - '**.php' @@ -13,15 +14,25 @@ on: jobs: build: if: ${{ github.ref != 'refs/heads/l10n_development' }} - runs-on: ubuntu-24.04 + runs-on: docker + container: + image: node:24-bullseye 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: actions/checkout@v4 + - uses: https://code.forgejo.org/actions/checkout@v6 - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: https://github.com/shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: gd, mbstring, json, curl, xml, mysql, ldap @@ -32,34 +43,31 @@ jobs: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer packages - uses: actions/cache@v4 + uses: https://code.forgejo.org/actions/cache@v5 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: + DB_HOST: mysql run: | php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing - name: Start migration:rollback test + env: + DB_HOST: mysql run: | php${{ matrix.php }} artisan migrate:rollback --force -n --database=mysql_testing - name: Start migration rerun test + env: + DB_HOST: mysql run: | php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing diff --git a/.github/workflows/test-php.yml b/.forgejo/workflows/test-php.yml similarity index 64% rename from .github/workflows/test-php.yml rename to .forgejo/workflows/test-php.yml index 5f4c16caf48..0fc39d9fafb 100644 --- a/.github/workflows/test-php.yml +++ b/.forgejo/workflows/test-php.yml @@ -1,6 +1,7 @@ name: test-php on: + workflow_dispatch: push: paths: - '**.php' @@ -13,15 +14,25 @@ on: jobs: build: if: ${{ github.ref != 'refs/heads/l10n_development' }} - runs-on: ubuntu-24.04 + runs-on: docker + container: + image: node:24-bullseye 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: actions/checkout@v4 + - uses: https://code.forgejo.org/actions/checkout@v6 - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: https://github.com/shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: gd, mbstring, json, curl, xml, mysql, ldap, gmp @@ -32,30 +43,25 @@ jobs: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer packages - uses: actions/cache@v4 + uses: https://code.forgejo.org/actions/cache@v5 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: + DB_HOST: mysql 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: + DB_HOST: mysql run: php${{ matrix.php }} ./vendor/bin/phpunit From 0f59981932ea927d52c490537e0eb55f4e351d0a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 27 Apr 2026 12:52:05 +0100 Subject: [PATCH 126/204] CI: Updated tests using DB to set test DB URL --- .forgejo/workflows/test-migrations.yml | 6 +++--- .forgejo/workflows/test-php.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.forgejo/workflows/test-migrations.yml b/.forgejo/workflows/test-migrations.yml index 7348ff2b335..089dbd67a1d 100644 --- a/.forgejo/workflows/test-migrations.yml +++ b/.forgejo/workflows/test-migrations.yml @@ -56,18 +56,18 @@ jobs: - name: Start migration test env: - DB_HOST: mysql + 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: - DB_HOST: mysql + 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: - DB_HOST: mysql + TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@mysql/bookstack-test' run: | php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing diff --git a/.forgejo/workflows/test-php.yml b/.forgejo/workflows/test-php.yml index 0fc39d9fafb..a06cdfa66f4 100644 --- a/.forgejo/workflows/test-php.yml +++ b/.forgejo/workflows/test-php.yml @@ -56,12 +56,12 @@ jobs: - name: Migrate and seed the database env: - DB_HOST: mysql + 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: - DB_HOST: mysql + TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@mysql/bookstack-test' run: php${{ matrix.php }} ./vendor/bin/phpunit From cc6e9e0546f2663d40a51039483d94d9814a57c7 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 27 Apr 2026 13:17:58 +0100 Subject: [PATCH 127/204] CI: Attempt a more robust avif support check --- tests/Uploads/ImageTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Uploads/ImageTest.php b/tests/Uploads/ImageTest.php index 1088e657ef4..1bccee2c395 100644 --- a/tests/Uploads/ImageTest.php +++ b/tests/Uploads/ImageTest.php @@ -75,7 +75,7 @@ public function test_image_display_thumbnail_generation_for_apng_images_uses_ori public function test_image_display_thumbnail_generation_for_animated_avif_images_uses_original_file() { - if (! function_exists('imageavif')) { + if ((gd_info()['AVIF Support'] ?? false) !== true) { $this->markTestSkipped('imageavif() is not available'); } From 2e2f59fa0f63d40be748ec905c29e76c16738ebd Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 27 Apr 2026 13:36:47 +0100 Subject: [PATCH 128/204] CI: Updated images to debian trixie --- .forgejo/workflows/analyse-php.yml | 2 +- .forgejo/workflows/lint-js.yml | 2 +- .forgejo/workflows/lint-php.yml | 2 +- .forgejo/workflows/test-js.yml | 2 +- .forgejo/workflows/test-migrations.yml | 2 +- .forgejo/workflows/test-php.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.forgejo/workflows/analyse-php.yml b/.forgejo/workflows/analyse-php.yml index 1214c39fbf5..8975d6e5296 100644 --- a/.forgejo/workflows/analyse-php.yml +++ b/.forgejo/workflows/analyse-php.yml @@ -14,7 +14,7 @@ jobs: if: ${{ github.ref != 'refs/heads/l10n_development' }} runs-on: docker container: - image: node:24-bullseye + image: docker.io/library/node:24-trixie steps: - uses: https://code.forgejo.org/actions/checkout@v6 diff --git a/.forgejo/workflows/lint-js.yml b/.forgejo/workflows/lint-js.yml index cef1d054074..5cacec67aab 100644 --- a/.forgejo/workflows/lint-js.yml +++ b/.forgejo/workflows/lint-js.yml @@ -16,7 +16,7 @@ jobs: if: ${{ github.ref != 'refs/heads/l10n_development' }} runs-on: docker container: - image: node:24-bullseye + image: docker.io/library/node:24-trixie steps: - uses: https://code.forgejo.org/actions/checkout@v6 diff --git a/.forgejo/workflows/lint-php.yml b/.forgejo/workflows/lint-php.yml index abebcc5eca2..b409c62e254 100644 --- a/.forgejo/workflows/lint-php.yml +++ b/.forgejo/workflows/lint-php.yml @@ -14,7 +14,7 @@ jobs: if: ${{ github.ref != 'refs/heads/l10n_development' }} runs-on: docker container: - image: node:24-bullseye + image: docker.io/library/node:24-trixie steps: - uses: https://code.forgejo.org/actions/checkout@v6 diff --git a/.forgejo/workflows/test-js.yml b/.forgejo/workflows/test-js.yml index 6fa21ee2714..180e6d54501 100644 --- a/.forgejo/workflows/test-js.yml +++ b/.forgejo/workflows/test-js.yml @@ -18,7 +18,7 @@ jobs: if: ${{ github.ref != 'refs/heads/l10n_development' }} runs-on: docker container: - image: node:24-bullseye + image: docker.io/library/node:24-trixie steps: - uses: https://code.forgejo.org/actions/checkout@v6 diff --git a/.forgejo/workflows/test-migrations.yml b/.forgejo/workflows/test-migrations.yml index 089dbd67a1d..e969d3e4721 100644 --- a/.forgejo/workflows/test-migrations.yml +++ b/.forgejo/workflows/test-migrations.yml @@ -16,7 +16,7 @@ jobs: if: ${{ github.ref != 'refs/heads/l10n_development' }} runs-on: docker container: - image: node:24-bullseye + image: docker.io/library/node:24-trixie strategy: matrix: php: ['8.2', '8.3', '8.4', '8.5'] diff --git a/.forgejo/workflows/test-php.yml b/.forgejo/workflows/test-php.yml index a06cdfa66f4..5ff2d14a5e0 100644 --- a/.forgejo/workflows/test-php.yml +++ b/.forgejo/workflows/test-php.yml @@ -16,7 +16,7 @@ jobs: if: ${{ github.ref != 'refs/heads/l10n_development' }} runs-on: docker container: - image: node:24-bullseye + image: docker.io/library/node:24-trixie strategy: matrix: php: ['8.2', '8.3', '8.4', '8.5'] From c1610c453298770db4c1100863f0aefb55a78eb3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 27 Apr 2026 17:48:27 +0100 Subject: [PATCH 129/204] Meta: Migrated repo content to forgejo Kept some GitHub templates with warnings about the migration. Made some initial updates to readme for the migration. --- .forgejo/CODE_OF_CONDUCT.md | 2 ++ .../ISSUE_TEMPLATE/api_request.yml | 0 {.github => .forgejo}/ISSUE_TEMPLATE/bug_report.yml | 0 .forgejo/ISSUE_TEMPLATE/config.yml | 13 +++++++++++++ .../ISSUE_TEMPLATE/feature_request.yml | 2 +- .../ISSUE_TEMPLATE/language_request.yml | 0 .../ISSUE_TEMPLATE/support_request.yml | 6 +++--- .../ISSUE_TEMPLATE/z_blank_request.yml | 0 {.github => .forgejo}/SECURITY.md | 0 .forgejo/pull_request_template.md | 11 +++++++++++ .github/ISSUE_TEMPLATE/config.yml | 6 +++--- .github/pull_request_template.md | 13 ++++++------- readme.md | 9 ++++----- 13 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 .forgejo/CODE_OF_CONDUCT.md rename {.github => .forgejo}/ISSUE_TEMPLATE/api_request.yml (100%) rename {.github => .forgejo}/ISSUE_TEMPLATE/bug_report.yml (100%) create mode 100644 .forgejo/ISSUE_TEMPLATE/config.yml rename {.github => .forgejo}/ISSUE_TEMPLATE/feature_request.yml (93%) rename {.github => .forgejo}/ISSUE_TEMPLATE/language_request.yml (100%) rename {.github => .forgejo}/ISSUE_TEMPLATE/support_request.yml (91%) rename {.github => .forgejo}/ISSUE_TEMPLATE/z_blank_request.yml (100%) rename {.github => .forgejo}/SECURITY.md (100%) create mode 100644 .forgejo/pull_request_template.md diff --git a/.forgejo/CODE_OF_CONDUCT.md b/.forgejo/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..7a02656725d --- /dev/null +++ b/.forgejo/CODE_OF_CONDUCT.md @@ -0,0 +1,2 @@ +Please find our community rules on our website here: +https://www.bookstackapp.com/about/community-rules/ \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/api_request.yml b/.forgejo/ISSUE_TEMPLATE/api_request.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/api_request.yml rename to .forgejo/ISSUE_TEMPLATE/api_request.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.forgejo/ISSUE_TEMPLATE/bug_report.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/bug_report.yml rename to .forgejo/ISSUE_TEMPLATE/bug_report.yml diff --git a/.forgejo/ISSUE_TEMPLATE/config.yml b/.forgejo/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..a72fb1ef4cb --- /dev/null +++ b/.forgejo/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,13 @@ +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. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.forgejo/ISSUE_TEMPLATE/feature_request.yml similarity index 93% rename from .github/ISSUE_TEMPLATE/feature_request.yml rename to .forgejo/ISSUE_TEMPLATE/feature_request.yml index ca1f2b8301c..c1420cb1933 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.forgejo/ISSUE_TEMPLATE/feature_request.yml @@ -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://github.com/BookStackApp/BookStack/issues?q=is%3Aissue) 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://codeberg.org/bookstack/bookstack/issues) 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 diff --git a/.github/ISSUE_TEMPLATE/language_request.yml b/.forgejo/ISSUE_TEMPLATE/language_request.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/language_request.yml rename to .forgejo/ISSUE_TEMPLATE/language_request.yml diff --git a/.github/ISSUE_TEMPLATE/support_request.yml b/.forgejo/ISSUE_TEMPLATE/support_request.yml similarity index 91% rename from .github/ISSUE_TEMPLATE/support_request.yml rename to .forgejo/ISSUE_TEMPLATE/support_request.yml index ae808a0b600..d60eab71101 100644 --- a/.github/ISSUE_TEMPLATE/support_request.yml +++ b/.forgejo/ISSUE_TEMPLATE/support_request.yml @@ -15,11 +15,11 @@ body: - type: checkboxes id: searchissue attributes: - label: Searched GitHub Issues + label: Searched Existing Issues description: | - I have searched for the issue and potential resolutions within the [project's GitHub issue list](https://github.com/BookStackApp/BookStack/issues) + I have searched for the issue and potential resolutions within the [project's issue list](https://codeberg.org/bookstack/bookstack/issues) options: - - label: I have searched GitHub for the issue. + - label: I have searched for the issue. required: true - type: textarea id: scenario diff --git a/.github/ISSUE_TEMPLATE/z_blank_request.yml b/.forgejo/ISSUE_TEMPLATE/z_blank_request.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/z_blank_request.yml rename to .forgejo/ISSUE_TEMPLATE/z_blank_request.yml diff --git a/.github/SECURITY.md b/.forgejo/SECURITY.md similarity index 100% rename from .github/SECURITY.md rename to .forgejo/SECURITY.md diff --git a/.forgejo/pull_request_template.md b/.forgejo/pull_request_template.md new file mode 100644 index 00000000000..70f1058748c --- /dev/null +++ b/.forgejo/pull_request_template.md @@ -0,0 +1,11 @@ +## Details + + + + +## Checklist + + + +- [ ] 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. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 019667388eb..0cd657d7a50 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: false contact_links: - - name: Discord Chat Support - url: https://discord.gg/ztkBqR2 - about: Realtime support & chat with the BookStack community and the team. + - 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: Debugging & Common Issues url: https://www.bookstackapp.com/docs/admin/debugging/ diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 70f1058748c..c185f3280b6 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,11 +1,10 @@ -## Details +**Warning:** - - +This project has migrated to Codeberg: +https://codeberg.org/bookstack/bookstack -## Checklist +Please open pull requests here instead. - +ANY PULL REQUESTS OPENED HERE WILL BE CLOSED WITHOUT COMMENT OR MERGE. -- [ ] 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. +--- \ No newline at end of file diff --git a/readme.md b/readme.md index 340545dccaf..2cc24c52e09 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # BookStack -[![GitHub release](https://img.shields.io/github/release/BookStackApp/BookStack.svg)](https://github.com/BookStackApp/BookStack/releases/latest) +[![Codeberg 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://github.com/BookStackApp/BookStack/workflows/test-php/badge.svg)](https://github.com/BookStackApp/BookStack/actions) @@ -11,7 +11,6 @@ [![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)
    [![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) @@ -23,7 +22,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://github.com/BookStackApp/BookStack/issues) +* [Issue List](https://codeberg.org/bookstack/bookstack/issues) * [Community Discussions](https://community.bookstackapp.com/) * [Support Options](https://www.bookstackapp.com/support/) @@ -134,7 +133,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](https://github.com/BookStackApp/BookStack/blob/development/.github/SECURITY.md). +If you would like to report a security concern, details of doing so [can be found here](/.forgejo/SECURITY.md). ## ♿ Accessibility @@ -142,7 +141,7 @@ 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 [BookStackApp/website](https://codeberg.org/bookstack/website) repo. +The website which contains the project docs & blog can be found in the [bookstack/website](https://codeberg.org/bookstack/website) repo. ## ⚖️ License From 7c1d30bc8fc1464b7bcbeae3e70fd91d672759f7 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 27 Apr 2026 20:56:05 +0100 Subject: [PATCH 130/204] Translations: Added crowdin workflow action --- .forgejo/workflows/sync-translations.yml | 36 ++++++++++++++++++++++++ crowdin.yml | 5 +++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 .forgejo/workflows/sync-translations.yml diff --git a/.forgejo/workflows/sync-translations.yml b/.forgejo/workflows/sync-translations.yml new file mode 100644 index 00000000000..5ff760220b1 --- /dev/null +++ b/.forgejo/workflows/sync-translations.yml @@ -0,0 +1,36 @@ +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: https://codeberg.org + env: + # A numeric ID, found at https://crowdin.com/project//tools/api + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + + # Visit https://crowdin.com/settings#api-key to create this token + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} \ No newline at end of file diff --git a/crowdin.yml b/crowdin.yml index b803b07eea1..53869eabdf3 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,10 +1,13 @@ 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: - - ":earth_africa: Translations" + - "Translations" + files: - source: /lang/en/*.php translation: /lang/%two_letters_code%/%original_file_name% From 24e6087ef8b54174e851dfd356632abad37ff551 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 27 Apr 2026 21:13:05 +0100 Subject: [PATCH 131/204] Meta: Updated readme shields and fixed workflow value --- .forgejo/workflows/sync-translations.yml | 5 +---- readme.md | 10 +++++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.forgejo/workflows/sync-translations.yml b/.forgejo/workflows/sync-translations.yml index 5ff760220b1..a0c09ba8f6b 100644 --- a/.forgejo/workflows/sync-translations.yml +++ b/.forgejo/workflows/sync-translations.yml @@ -27,10 +27,7 @@ jobs: download_translations: true localization_branch_name: l10n_development create_pull_request: false - github_base_url: https://codeberg.org + github_base_url: codeberg.org env: - # A numeric ID, found at https://crowdin.com/project//tools/api CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} - - # Visit https://crowdin.com/settings#api-key to create this token CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} \ No newline at end of file diff --git a/readme.md b/readme.md index 2cc24c52e09..7bb71949d92 100644 --- a/readme.md +++ b/readme.md @@ -1,14 +1,14 @@ # BookStack -[![Codeberg 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) +[![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) [![Crowdin](https://badges.crowdin.net/bookstack/localized.svg)](https://crowdin.com/project/bookstack) -[![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) +[![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) [![php-metrics](https://img.shields.io/static/v1?label=Metrics&message=php&color=4F5B93)](https://source.bookstackapp.com/php-stats/index.html)
    [![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/) +[![Repo Stats](https://img.shields.io/static/v1?label=Code+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)
    From 55317039acd8c94f0d4bf0a275c865ed618a8f30 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 28 Apr 2026 09:30:48 +0100 Subject: [PATCH 132/204] Meta: Converted GitHub references in codebase to Codeberg --- .forgejo/SECURITY.md | 2 +- app/App/PwaManifestBuilder.php | 2 +- dev/docker/db-testing/Dockerfile | 2 +- dev/docs/logical-theme-system.md | 2 +- dev/docs/release-process.md | 6 +++--- dev/docs/wysiwyg-js-api.md | 2 +- readme.md | 11 +++++------ .../views/api-docs/parts/getting-started.blade.php | 4 ++-- resources/views/errors/debug.blade.php | 6 +++--- resources/views/settings/layout.blade.php | 2 +- tests/Entity/PageContentTest.php | 2 +- 11 files changed, 20 insertions(+), 21 deletions(-) diff --git a/.forgejo/SECURITY.md b/.forgejo/SECURITY.md index 12e5fe04c6d..f426079e839 100644 --- a/.forgejo/SECURITY.md +++ b/.forgejo/SECURITY.md @@ -2,7 +2,7 @@ ## Supported Versions -Only the [latest version](https://github.com/BookStackApp/BookStack/releases) of BookStack is supported. +Only the [latest version](https://codeberg.org/bookstack/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. diff --git a/app/App/PwaManifestBuilder.php b/app/App/PwaManifestBuilder.php index 81ab2fcd711..2dbaead1373 100644 --- a/app/App/PwaManifestBuilder.php +++ b/app/App/PwaManifestBuilder.php @@ -10,7 +10,7 @@ public function build(): array // 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://github.com/BookStackApp/BookStack/issues/4649 + // Context: https://codeberg.org/bookstack/bookstack/issues/4649 $darkMode = (bool) setting()->getForCurrentUser('dark-mode-enabled'); $appName = setting('app-name'); diff --git a/dev/docker/db-testing/Dockerfile b/dev/docker/db-testing/Dockerfile index 618f5bb8281..411e2197b67 100644 --- a/dev/docker/db-testing/Dockerfile +++ b/dev/docker/db-testing/Dockerfile @@ -18,7 +18,7 @@ ARG BRANCH=development # Download BookStack & install PHP deps RUN mkdir -p /var/www && \ - git clone https://github.com/bookstackapp/bookstack.git --branch "$BRANCH" --single-branch /var/www/bookstack && \ + git clone https://codeberg.org/bookstack/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 diff --git a/dev/docs/logical-theme-system.md b/dev/docs/logical-theme-system.md index 9457ca78b52..b6342ce5465 100644 --- a/dev/docs/logical-theme-system.md +++ b/dev/docs/logical-theme-system.md @@ -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 GitHub here](https://github.com/BookStackApp/BookStack/blob/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 Codeberg here](https://codeberg.org/bookstack/bookstack/src/branch/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. diff --git a/dev/docs/release-process.md b/dev/docs/release-process.md index 758d6db4cfc..75e9a5ae61d 100644 --- a/dev/docs/release-process.md +++ b/dev/docs/release-process.md @@ -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://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. +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. ### 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 [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). +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). ### 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://github.com/BookStackApp/devops/blob/main/meta-scripts/bookstack-release-steps) devops repo which provides the steps and commands for deploying a new release. \ No newline at end of file +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. \ No newline at end of file diff --git a/dev/docs/wysiwyg-js-api.md b/dev/docs/wysiwyg-js-api.md index 4b4fafe5624..869bf8c492a 100644 --- a/dev/docs/wysiwyg-js-api.md +++ b/dev/docs/wysiwyg-js-api.md @@ -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://github.com/BookStackApp/BookStack/issues/5937 +Feedback is very much welcomed via this issue: https://codeberg.org/bookstack/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 diff --git a/readme.md b/readme.md index 7bb71949d92..8b81abaee64 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,6 @@ [![php-metrics](https://img.shields.io/static/v1?label=Metrics&message=php&color=4F5B93)](https://source.bookstackapp.com/php-stats/index.html)
    [![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=Code+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)
    @@ -112,13 +111,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://github.com/BookStackApp/BookStack/issues/new?template=language_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://codeberg.org/bookstack/bookstack/issues/new?template=.forgejo%2fISSUE_TEMPLATE%2flanguage_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 GitHub, 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 code, is not advised. ## 🎁 Contributing, Issues & Pull Requests -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. +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. 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. @@ -145,14 +144,14 @@ The website which contains the project docs & blog can be found in the [bookstac ## ⚖️ License -The BookStack source is provided under the [MIT License](https://github.com/BookStackApp/BookStack/blob/development/LICENSE). +The BookStack source is provided under the [MIT License](https://codeberg.org/bookstack/bookstack/src/branch/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://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). +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). 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. diff --git a/resources/views/api-docs/parts/getting-started.blade.php b/resources/views/api-docs/parts/getting-started.blade.php index ebe3838ef1f..8a14befa55a 100644 --- a/resources/views/api-docs/parts/getting-started.blade.php +++ b/resources/views/api-docs/parts/getting-started.blade.php @@ -14,11 +14,11 @@ HTTP POST calls upon events occurring in BookStack.
  • - Visual Theme System - + Visual Theme System - Methods to override views, translations and icons within BookStack.
  • - Logical Theme System - + Logical Theme System - Methods to extend back-end functionality within BookStack.
  • diff --git a/resources/views/errors/debug.blade.php b/resources/views/errors/debug.blade.php index e7155431c86..969c49595ef 100644 --- a/resources/views/errors/debug.blade.php +++ b/resources/views/errors/debug.blade.php @@ -113,13 +113,13 @@ Review BookStack debugging documentation »
  • - Ensure your instance is up-to-date » + Ensure your instance is up-to-date »
  • - Search for the issue on GitHub » + Search for the issue on GitHub »
  • - Ask for help via Discord » + Ask for help in our community forums »
  • Search the error message » diff --git a/resources/views/settings/layout.blade.php b/resources/views/settings/layout.blade.php index 930d407a508..2ca06b86ac0 100644 --- a/resources/views/settings/layout.blade.php +++ b/resources/views/settings/layout.blade.php @@ -18,7 +18,7 @@
    {{ trans('settings.system_version') }}
    - + BookStack @if(!str_starts_with($version, 'v')) version @endif {{ $version }}
    diff --git a/tests/Entity/PageContentTest.php b/tests/Entity/PageContentTest.php index deae153e192..4d97e6b5961 100644 --- a/tests/Entity/PageContentTest.php +++ b/tests/Entity/PageContentTest.php @@ -370,7 +370,7 @@ public function test_base64_images_get_extracted_when_containing_whitespace() public function test_base64_images_within_html_blanked_if_not_supported_extension_for_extract() { - // Relevant to https://github.com/BookStackApp/BookStack/issues/3010 and other cases + // Relevant to https://codeberg.org/bookstack/bookstack/issues/3010 and other cases $extensions = [ 'jiff', 'pngr', 'png ', ' png', '.png', 'png.', 'p.ng', ',png', 'data:image/png', ',data:image/png', From fc220dea3996785a683b2661a711952f53f8da8d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 29 Apr 2026 18:07:32 +0100 Subject: [PATCH 133/204] Search: Fixed exact saerch term negation causing no results Closes #6121 --- app/Search/SearchRunner.php | 10 ++++++++-- tests/Search/EntitySearchTest.php | 4 ++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/Search/SearchRunner.php b/app/Search/SearchRunner.php index bfb65cf0f40..3912541723f 100644 --- a/app/Search/SearchRunner.php +++ b/app/Search/SearchRunner.php @@ -120,8 +120,14 @@ protected function buildQuery(SearchOptions $searchOpts, array $entityTypes): El $filter = function (EloquentBuilder $query) use ($exact) { $inputTerm = str_replace('\\', '\\\\', $exact->value); $query->where('name', 'like', '%' . $inputTerm . '%') - ->orWhere('description', 'like', '%' . $inputTerm . '%') - ->orWhere('text', '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 . '%'); + }); }; $exact->negated ? $entityQuery->whereNot($filter) : $entityQuery->where($filter); diff --git a/tests/Search/EntitySearchTest.php b/tests/Search/EntitySearchTest.php index cb1149dd10b..fc300241bcf 100644 --- a/tests/Search/EntitySearchTest.php +++ b/tests/Search/EntitySearchTest.php @@ -136,17 +136,21 @@ public function test_negated_searches() $page->tags()->saveMany([new Tag(['name' => 'DonkCount', 'value' => '500'])]); $page->created_by = $this->users->admin()->id; $page->save(); + $otherPage = $this->entities->newPage(['name' => 'A different page in negation tests', 'html' => '

    A different page in negation tests

    ']); $editor = $this->users->editor(); $this->actingAs($editor); $exactSearch = $this->get('/search?term=' . urlencode('negation -"tortoise"')); $exactSearch->assertStatus(200)->assertDontSeeText($page->name); + $exactSearch->assertSeeText($otherPage->name); $tagSearchA = $this->get('/search?term=' . urlencode('negation [DonkCount=500]')); $tagSearchA->assertStatus(200)->assertSeeText($page->name); + $tagSearchA->assertDontSeeText($otherPage->name); $tagSearchB = $this->get('/search?term=' . urlencode('negation -[DonkCount=500]')); $tagSearchB->assertStatus(200)->assertDontSeeText($page->name); + $tagSearchB->assertSeeText($otherPage->name); $filterSearchA = $this->get('/search?term=' . urlencode('negation -{created_by:me}')); $filterSearchA->assertStatus(200)->assertSeeText($page->name); From 99a704698d1d46451ff14d78abc9d4a836f3f964 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 29 Apr 2026 18:12:24 +0100 Subject: [PATCH 134/204] Deps: Updated PHP package versions --- composer.lock | 254 +++++++++++++++++++++++++------------------------- 1 file changed, 127 insertions(+), 127 deletions(-) diff --git a/composer.lock b/composer.lock index a70aa9a0ff9..4a56da48823 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.376.3", + "version": "3.379.8", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "2081f8db174df4bb8842aed3b7b513590ee9d219" + "reference": "856ddf3d241c29132fe1eb946e112351ab043542" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2081f8db174df4bb8842aed3b7b513590ee9d219", - "reference": "2081f8db174df4bb8842aed3b7b513590ee9d219", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/856ddf3d241c29132fe1eb946e112351ab043542", + "reference": "856ddf3d241c29132fe1eb946e112351ab043542", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.376.3" + "source": "https://github.com/aws/aws-sdk-php/tree/3.379.8" }, - "time": "2026-04-03T18:07:33+00:00" + "time": "2026-04-27T19:13:21+00:00" }, { "name": "bacon/bacon-qr-code", @@ -985,12 +985,12 @@ "version": "v7.0.5", "source": { "type": "git", - "url": "https://github.com/firebase/php-jwt.git", + "url": "https://github.com/googleapis/php-jwt.git", "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380", "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380", "shasum": "" }, @@ -1039,8 +1039,8 @@ "php" ], "support": { - "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v7.0.5" + "issues": "https://github.com/googleapis/php-jwt/issues", + "source": "https://github.com/googleapis/php-jwt/tree/v7.0.5" }, "time": "2026-04-01T20:38:03+00:00" }, @@ -1802,16 +1802,16 @@ }, { "name": "laravel/framework", - "version": "v12.56.0", + "version": "v12.58.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "dac16d424b59debb2273910dde88eb7050a2a709" + "reference": "6172ae1f44ba5d89e111057ee4a4e7c27f5a610d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/dac16d424b59debb2273910dde88eb7050a2a709", - "reference": "dac16d424b59debb2273910dde88eb7050a2a709", + "url": "https://api.github.com/repos/laravel/framework/zipball/6172ae1f44ba5d89e111057ee4a4e7c27f5a610d", + "reference": "6172ae1f44ba5d89e111057ee4a4e7c27f5a610d", "shasum": "" }, "require": { @@ -1852,8 +1852,8 @@ "symfony/mailer": "^7.2.0", "symfony/mime": "^7.2.0", "symfony/polyfill-php83": "^1.33", - "symfony/polyfill-php84": "^1.33", - "symfony/polyfill-php85": "^1.33", + "symfony/polyfill-php84": "^1.34", + "symfony/polyfill-php85": "^1.34", "symfony/process": "^7.2.0", "symfony/routing": "^7.2.0", "symfony/uid": "^7.2.0", @@ -2020,20 +2020,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-03-26T14:51:54+00:00" + "time": "2026-04-26T16:42:04+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.16", + "version": "v0.3.17", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "11e7d5f93803a2190b00e145142cb00a33d17ad2" + "reference": "6a82ac19a28b916ae0885828795dbd4c59d9a818" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/11e7d5f93803a2190b00e145142cb00a33d17ad2", - "reference": "11e7d5f93803a2190b00e145142cb00a33d17ad2", + "url": "https://api.github.com/repos/laravel/prompts/zipball/6a82ac19a28b916ae0885828795dbd4c59d9a818", + "reference": "6a82ac19a28b916ae0885828795dbd4c59d9a818", "shasum": "" }, "require": { @@ -2077,22 +2077,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.16" + "source": "https://github.com/laravel/prompts/tree/v0.3.17" }, - "time": "2026-03-23T14:35:33+00:00" + "time": "2026-04-20T16:07:33+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.10", + "version": "v2.0.13", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669" + "reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669", - "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b566ee0dd251f3c4078bed003a7ce015f5ea6dce", + "reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce", "shasum": "" }, "require": { @@ -2140,20 +2140,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2026-02-20T19:59:49+00:00" + "time": "2026-04-16T14:03:50+00:00" }, { "name": "laravel/socialite", - "version": "v5.26.1", + "version": "v5.27.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "db6ec2ee967b7f06412c3a0cf1daaf072f4752a4" + "reference": "40e0757a75637c7b2dff05d3286b0d8fc25e5c0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/db6ec2ee967b7f06412c3a0cf1daaf072f4752a4", - "reference": "db6ec2ee967b7f06412c3a0cf1daaf072f4752a4", + "url": "https://api.github.com/repos/laravel/socialite/zipball/40e0757a75637c7b2dff05d3286b0d8fc25e5c0e", + "reference": "40e0757a75637c7b2dff05d3286b0d8fc25e5c0e", "shasum": "" }, "require": { @@ -2212,7 +2212,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2026-03-29T14:50:53+00:00" + "time": "2026-04-24T14:05:47+00:00" }, { "name": "laravel/tinker", @@ -3362,16 +3362,16 @@ }, { "name": "nesbot/carbon", - "version": "3.11.3", + "version": "3.11.4", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "6a7e652845bb018c668220c2a545aded8594fbbf" + "reference": "e890471a3494740f7d9326d72ce6a8c559ffee60" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6a7e652845bb018c668220c2a545aded8594fbbf", - "reference": "6a7e652845bb018c668220c2a545aded8594fbbf", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/e890471a3494740f7d9326d72ce6a8c559ffee60", + "reference": "e890471a3494740f7d9326d72ce6a8c559ffee60", "shasum": "" }, "require": { @@ -3463,7 +3463,7 @@ "type": "tidelift" } ], - "time": "2026-03-11T17:23:39+00:00" + "time": "2026-04-07T09:57:54+00:00" }, { "name": "nette/schema", @@ -4028,16 +4028,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.50", + "version": "3.0.52", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b" + "reference": "2adaefc83df2ec548558307690f376dd7d4f4fce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/aa6ad8321ed103dc3624fb600a25b66ebf78ec7b", - "reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2adaefc83df2ec548558307690f376dd7d4f4fce", + "reference": "2adaefc83df2ec548558307690f376dd7d4f4fce", "shasum": "" }, "require": { @@ -4118,7 +4118,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.50" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.52" }, "funding": [ { @@ -4134,7 +4134,7 @@ "type": "tidelift" } ], - "time": "2026-03-19T02:57:58+00:00" + "time": "2026-04-27T07:02:15+00:00" }, { "name": "pragmarx/google2fa", @@ -6499,16 +6499,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { @@ -6558,7 +6558,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -6578,20 +6578,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", "shasum": "" }, "require": { @@ -6640,7 +6640,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" }, "funding": [ { @@ -6660,11 +6660,11 @@ "type": "tidelift" } ], - "time": "2025-06-27T09:58:17+00:00" + "time": "2026-04-26T13:13:48+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", @@ -6727,7 +6727,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.37.0" }, "funding": [ { @@ -6751,7 +6751,7 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -6812,7 +6812,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" }, "funding": [ { @@ -6836,16 +6836,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", "shasum": "" }, "require": { @@ -6897,7 +6897,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0" }, "funding": [ { @@ -6917,20 +6917,20 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2026-04-10T17:25:58+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", "shasum": "" }, "require": { @@ -6981,7 +6981,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" }, "funding": [ { @@ -7001,20 +7001,20 @@ "type": "tidelift" } ], - "time": "2025-01-02T08:10:11+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149", "shasum": "" }, "require": { @@ -7061,7 +7061,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" }, "funding": [ { @@ -7081,20 +7081,20 @@ "type": "tidelift" } ], - "time": "2025-07-08T02:45:35+00:00" + "time": "2026-04-10T17:25:58+00:00" }, { "name": "symfony/polyfill-php84", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/88486db2c389b290bf87ff1de7ebc1e13e42bb06", + "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06", "shasum": "" }, "require": { @@ -7141,7 +7141,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.37.0" }, "funding": [ { @@ -7161,20 +7161,20 @@ "type": "tidelift" } ], - "time": "2025-06-24T13:30:11+00:00" + "time": "2026-04-10T18:47:49+00:00" }, { "name": "symfony/polyfill-php85", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", - "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee", + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee", "shasum": "" }, "require": { @@ -7221,7 +7221,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.37.0" }, "funding": [ { @@ -7241,20 +7241,20 @@ "type": "tidelift" } ], - "time": "2025-06-23T16:12:55+00:00" + "time": "2026-04-26T13:10:57+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", - "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94", "shasum": "" }, "require": { @@ -7304,7 +7304,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.37.0" }, "funding": [ { @@ -7324,7 +7324,7 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/process", @@ -8285,23 +8285,23 @@ }, { "name": "voku/portable-ascii", - "version": "2.0.3", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + "reference": "8e1051fe39379367aecf014f41744ce7539a856f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", - "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/8e1051fe39379367aecf014f41744ce7539a856f", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": ">=7.1.0" }, "require-dev": { - "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + "phpunit/phpunit": "~8.5 || ~9.6 || ~10.5 || ~11.5" }, "suggest": { "ext-intl": "Use Intl for transliterator_transliterate() support" @@ -8331,7 +8331,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + "source": "https://github.com/voku/portable-ascii/tree/2.1.1" }, "funding": [ { @@ -8355,7 +8355,7 @@ "type": "tidelift" } ], - "time": "2024-11-21T01:49:47+00:00" + "time": "2026-04-26T05:33:54+00:00" }, { "name": "xemlock/htmlpurifier-html5", @@ -8723,16 +8723,16 @@ }, { "name": "larastan/larastan", - "version": "v3.9.3", + "version": "v3.9.6", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65" + "reference": "9ad17e83e96b63536cb6ac39c3d40d29ff9cf636" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", - "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", + "url": "https://api.github.com/repos/larastan/larastan/zipball/9ad17e83e96b63536cb6ac39c3d40d29ff9cf636", + "reference": "9ad17e83e96b63536cb6ac39c3d40d29ff9cf636", "shasum": "" }, "require": { @@ -8746,7 +8746,7 @@ "illuminate/pipeline": "^11.44.2 || ^12.4.1 || ^13", "illuminate/support": "^11.44.2 || ^12.4.1 || ^13", "php": "^8.2", - "phpstan/phpstan": "^2.1.32" + "phpstan/phpstan": "^2.1.44" }, "require-dev": { "doctrine/coding-standard": "^13", @@ -8801,7 +8801,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.9.3" + "source": "https://github.com/larastan/larastan/tree/v3.9.6" }, "funding": [ { @@ -8809,7 +8809,7 @@ "type": "github" } ], - "time": "2026-02-20T12:07:12+00:00" + "time": "2026-04-16T10:02:43+00:00" }, { "name": "mockery/mockery", @@ -8956,23 +8956,23 @@ }, { "name": "nunomaduro/collision", - "version": "v8.9.2", + "version": "v8.9.4", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "6eb16883e74fd725ac64dbe81544c961ab448ba5" + "reference": "716af8f95a470e9094cfca09ed897b023be191a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/6eb16883e74fd725ac64dbe81544c961ab448ba5", - "reference": "6eb16883e74fd725ac64dbe81544c961ab448ba5", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/716af8f95a470e9094cfca09ed897b023be191a5", + "reference": "716af8f95a470e9094cfca09ed897b023be191a5", "shasum": "" }, "require": { "filp/whoops": "^2.18.4", "nunomaduro/termwind": "^2.4.0", "php": "^8.2.0", - "symfony/console": "^7.4.8 || ^8.0.4" + "symfony/console": "^7.4.8 || ^8.0.8" }, "conflict": { "laravel/framework": "<11.48.0 || >=14.0.0", @@ -8980,12 +8980,12 @@ }, "require-dev": { "brianium/paratest": "^7.8.5", - "larastan/larastan": "^3.9.3", - "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.2.0", - "laravel/pint": "^1.29.0", - "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.0.0", + "larastan/larastan": "^3.9.6", + "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.5.0", + "laravel/pint": "^1.29.1", + "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.2.1", "pestphp/pest": "^3.8.5 || ^4.4.3 || ^5.0.0", - "sebastian/environment": "^7.2.1 || ^8.0.4 || ^9.0.0" + "sebastian/environment": "^7.2.1 || ^8.0.4 || ^9.3.0" }, "type": "library", "extra": { @@ -9048,7 +9048,7 @@ "type": "patreon" } ], - "time": "2026-03-31T21:51:27+00:00" + "time": "2026-04-21T14:04:20+00:00" }, { "name": "phar-io/manifest", @@ -9170,11 +9170,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.46", + "version": "2.1.54", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a193923fc2d6325ef4e741cf3af8c3e8f54dbf25", - "reference": "a193923fc2d6325ef4e741cf3af8c3e8f54dbf25", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8be50c3992107dc837b17da4d140fbbdf9a5c5bd", + "reference": "8be50c3992107dc837b17da4d140fbbdf9a5c5bd", "shasum": "" }, "require": { @@ -9219,7 +9219,7 @@ "type": "github" } ], - "time": "2026-04-01T09:25:14+00:00" + "time": "2026-04-29T13:31:09+00:00" }, { "name": "phpunit/php-code-coverage", @@ -10983,5 +10983,5 @@ "platform-overrides": { "php": "8.2.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } From fddeb9030b6c65ea848d685de51f61f90ff4e21a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 29 Apr 2026 18:31:11 +0100 Subject: [PATCH 135/204] Attachments: Added page access check to attachment delete Thanks to github.com/404-pkj for reporting. --- app/Uploads/Controllers/AttachmentController.php | 10 ++++++++-- tests/Uploads/AttachmentTest.php | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/Uploads/Controllers/AttachmentController.php b/app/Uploads/Controllers/AttachmentController.php index 9c60fa415f8..aeee1f52881 100644 --- a/app/Uploads/Controllers/AttachmentController.php +++ b/app/Uploads/Controllers/AttachmentController.php @@ -195,6 +195,7 @@ public function sortForPage(Request $request, int $pageId) $this->validate($request, [ 'order' => ['required', 'array'], ]); + $page = $this->pageQueries->findVisibleByIdOrFail($pageId); $this->checkOwnablePermission(Permission::PageUpdate, $page); @@ -221,8 +222,6 @@ public function get(Request $request, string $attachmentId) throw new NotFoundException(trans('errors.attachment_not_found')); } - $this->checkOwnablePermission(Permission::PageView, $page); - if ($attachment->external) { return redirect($attachment->path); } @@ -247,6 +246,13 @@ public function delete(string $attachmentId) { /** @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); diff --git a/tests/Uploads/AttachmentTest.php b/tests/Uploads/AttachmentTest.php index 945fc258d77..2d402c34006 100644 --- a/tests/Uploads/AttachmentTest.php +++ b/tests/Uploads/AttachmentTest.php @@ -5,6 +5,7 @@ use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\PageRepo; use BookStack\Entities\Tools\TrashCan; +use BookStack\Permissions\Permission; use BookStack\Uploads\Attachment; use Tests\TestCase; @@ -206,6 +207,21 @@ public function test_attachment_deletion_on_page_deletion() $this->files->deleteAllAttachmentFiles(); } + public function test_attachment_deletion_requires_page_access() + { + $page = $this->entities->page(); + $attachment = Attachment::factory()->create(['uploaded_to' => $page->id]); + $editor = $this->users->editor(); + + $this->permissions->disableEntityInheritedPermissions($page); + $this->permissions->grantUserRolePermissions($editor, [Permission::AttachmentDeleteAll]); + + $resp = $this->actingAs($editor)->delete($attachment->getUrl()); + $resp->assertNotFound(); + + $this->assertDatabaseHas('attachments', ['id' => $attachment->id]); + } + public function test_attachment_access_without_permission_shows_404() { $admin = $this->users->admin(); From 3ddfa9b94838a19840bee477e8cb017ff9804b34 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 30 Apr 2026 00:32:27 +0100 Subject: [PATCH 136/204] Meta: Updated security info and fixed some tests/links --- .forgejo/SECURITY.md | 10 ++++------ readme.md | 2 +- tests/DebugViewTest.php | 2 +- version | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.forgejo/SECURITY.md b/.forgejo/SECURITY.md index f426079e839..5c044c94629 100644 --- a/.forgejo/SECURITY.md +++ b/.forgejo/SECURITY.md @@ -12,16 +12,14 @@ 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, in a development-only branch) -feel free to raise it via a standard GitHub bug report issue. +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 the issue could have a security impact to BookStack instances, -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 directly contact the lead maintainer via email Dan Brown using the [details found here](https://www.bookstackapp.com/links/contact/). 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! \ No newline at end of file diff --git a/readme.md b/readme.md index 8b81abaee64..30d4d17891f 100644 --- a/readme.md +++ b/readme.md @@ -132,7 +132,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](.forgejo/SECURITY.md). ## ♿ Accessibility diff --git a/tests/DebugViewTest.php b/tests/DebugViewTest.php index 34de6b80297..b9a99ec725e 100644 --- a/tests/DebugViewTest.php +++ b/tests/DebugViewTest.php @@ -27,7 +27,7 @@ public function test_debug_view_shows_expected_details() $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=is%3Aissue+' . urlencode('An error occurred during testing') . '"]'); + $this->withHtml($resp)->assertElementExists('a[href*="?q=' . urlencode('An error occurred during testing') . '"]'); } public function test_debug_view_only_shows_when_debug_mode_is_enabled() diff --git a/version b/version index 14f310dc37f..9eadc470b49 100644 --- a/version +++ b/version @@ -1 +1 @@ -v26.01-dev +v26.05-dev From cf648906e94a22f802898ba38236edd09a3909e4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 30 Apr 2026 09:31:56 +0100 Subject: [PATCH 137/204] SSR: Hardened URL validator against a range of workarounds Added a more comprehensive range of tests to cover. Thanks to naruhodoowl (https://github.com/kilhsrito-crypto) for reporting. --- app/Util/SsrUrlValidator.php | 29 +++++- tests/Unit/SsrUrlValidatorTest.php | 62 ------------- tests/Util/SsrUrlValidatorTest.php | 142 +++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 65 deletions(-) delete mode 100644 tests/Unit/SsrUrlValidatorTest.php create mode 100644 tests/Util/SsrUrlValidatorTest.php diff --git a/app/Util/SsrUrlValidator.php b/app/Util/SsrUrlValidator.php index 076a653fc86..a9b5c138df5 100644 --- a/app/Util/SsrUrlValidator.php +++ b/app/Util/SsrUrlValidator.php @@ -8,6 +8,10 @@ * 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 { @@ -48,15 +52,34 @@ protected function urlMatchesPattern($url, $pattern): bool { $pattern = rtrim(trim($pattern), '/'); $url = trim($url); + $urlParts = parse_url($url); - if (empty($pattern) || empty($url)) { + if (empty($pattern) || empty($url) || $urlParts === false) { 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); - return preg_match('/^' . $regexPattern . '($|\/.*$|#.*$)/i', $url); + // Check against our URL + return preg_match('/^' . $regexPattern . '($|\/.*$)/i', $url); } /** diff --git a/tests/Unit/SsrUrlValidatorTest.php b/tests/Unit/SsrUrlValidatorTest.php deleted file mode 100644 index 8fb538916aa..00000000000 --- a/tests/Unit/SsrUrlValidatorTest.php +++ /dev/null @@ -1,62 +0,0 @@ - '', 'url' => '', 'result' => false], - ['config' => '', 'url' => 'https://example.com', 'result' => false], - ['config' => ' ', 'url' => 'https://example.com', 'result' => false], - ['config' => '*', 'url' => '', 'result' => false], - ['config' => '*', 'url' => 'https://example.com', 'result' => true], - ['config' => 'https://*', 'url' => 'https://example.com', 'result' => true], - ['config' => 'http://*', 'url' => 'https://example.com', 'result' => false], - ['config' => 'https://*example.com', 'url' => 'https://example.com', 'result' => true], - ['config' => 'https://*ample.com', 'url' => 'https://example.com', 'result' => true], - ['config' => 'https://*.example.com', 'url' => 'https://example.com', 'result' => false], - ['config' => 'https://*.example.com', 'url' => 'https://test.example.com', 'result' => true], - ['config' => '*//example.com', 'url' => 'https://example.com', 'result' => true], - ['config' => '*//example.com', 'url' => 'http://example.com', 'result' => true], - ['config' => '*//example.co', 'url' => 'http://example.co.uk', 'result' => false], - ['config' => '*//example.co/bookstack', 'url' => 'https://example.co/bookstack/a/path', 'result' => true], - ['config' => '*//example.co*', 'url' => 'https://example.co.uk/bookstack/a/path', 'result' => true], - ['config' => 'https://example.com', 'url' => 'https://example.com/a/b/c?test=cat', 'result' => true], - ['config' => 'https://example.com', 'url' => 'https://example.co.uk', 'result' => false], - - // Escapes - ['config' => 'https://(.*?).com', 'url' => 'https://example.com', 'result' => false], - ['config' => 'https://example.com', 'url' => 'https://example.co.uk#https://example.com', 'result' => false], - - // Multi values - ['config' => '*//example.org *//example.com', 'url' => 'https://example.com', 'result' => true], - ['config' => '*//example.org *//example.com', 'url' => 'https://example.com/a/b/c?test=cat#hello', 'result' => true], - ['config' => '*.example.org *.example.com', 'url' => 'https://example.co.uk', 'result' => false], - ['config' => ' *.example.org *.example.com ', 'url' => 'https://example.co.uk', 'result' => false], - ['config' => '* *.example.com', 'url' => 'https://example.co.uk', 'result' => true], - ['config' => '*//example.org *//example.com *//example.co.uk', 'url' => 'https://example.co.uk', 'result' => true], - ['config' => '*//example.org *//example.com *//example.co.uk', 'url' => 'https://example.net', 'result' => false], - ]; - - foreach ($testMap as $test) { - $result = (new SsrUrlValidator($test['config']))->allowed($test['url']); - $this->assertEquals($test['result'], $result, "Failed asserting url '{$test['url']}' with config '{$test['config']}' results " . ($test['result'] ? 'true' : 'false')); - } - } - - public function test_enssure_allowed() - { - $result = (new SsrUrlValidator('https://example.com'))->ensureAllowed('https://example.com'); - $this->assertNull($result); - - $this->expectException(HttpFetchException::class); - (new SsrUrlValidator('https://example.com'))->ensureAllowed('https://test.example.com'); - } -} diff --git a/tests/Util/SsrUrlValidatorTest.php b/tests/Util/SsrUrlValidatorTest.php new file mode 100644 index 00000000000..12e1765171e --- /dev/null +++ b/tests/Util/SsrUrlValidatorTest.php @@ -0,0 +1,142 @@ +set([ + 'app.ssr_hosts' => 'https://donkey.example.com', + ]); + + $validator = new SsrUrlValidator(); + + $this->assertTrue($validator->allowed('https://donkey.example.com')); + $this->assertFalse($validator->allowed('https://monkey.example.com')); + } + + public function test_config_string_can_be_passed_in_constructor() + { + config()->set([ + 'app.ssr_hosts' => 'https://donkey.example.com', + ]); + + $validator = new SsrUrlValidator('https://monkey.example.com'); + + $this->assertFalse($validator->allowed('https://donkey.example.com')); + $this->assertTrue($validator->allowed('https://monkey.example.com')); + } + + public function test_config_string_can_include_multiple_space_seperated_values() + { + $validator = new SsrUrlValidator('https://monkey.example.com https://cat.example.com'); + + $this->assertFalse($validator->allowed('https://donkey.example.com')); + $this->assertTrue($validator->allowed('https://monkey.example.com')); + $this->assertTrue($validator->allowed('https://cat.example.com')); + } + + public function test_ensure_allowed_throws_if_not_allowed() + { + $validator = new SsrUrlValidator('https://monkey.example.com'); + + $this->assertNull($validator->ensureAllowed('https://monkey.example.com')); + + $this->assertThrows(function () use ($validator) { + $validator->ensureAllowed('https://donkey.example.com'); + }, HttpFetchException::class, 'The URL does not match the configured allowed SSR hosts'); + } + + public function test_basic_url_matching() + { + $tests = [ + // Single values + ['config' => '', 'url' => '', 'result' => false], + ['config' => '', 'url' => 'https://example.com', 'result' => false], + ['config' => ' ', 'url' => 'https://example.com', 'result' => false], + ['config' => '*', 'url' => '', 'result' => false], + ['config' => '*', 'url' => 'https://example.com', 'result' => true], + ['config' => 'https://*', 'url' => 'https://example.com', 'result' => true], + ['config' => 'http://*', 'url' => 'https://example.com', 'result' => false], + ['config' => 'https://*example.com', 'url' => 'https://example.com', 'result' => true], + ['config' => 'https://*ample.com', 'url' => 'https://example.com', 'result' => true], + ['config' => 'https://*.example.com', 'url' => 'https://example.com', 'result' => false], + ['config' => 'https://*.example.com', 'url' => 'https://test.example.com', 'result' => true], + ['config' => '*//example.com', 'url' => 'https://example.com', 'result' => true], + ['config' => '*//example.com', 'url' => 'http://example.com', 'result' => true], + ['config' => '*//example.co', 'url' => 'http://example.co.uk', 'result' => false], + ['config' => '*//example.co/bookstack', 'url' => 'https://example.co/bookstack/a/path', 'result' => true], + ['config' => '*//example.co*', 'url' => 'https://example.co.uk/bookstack/a/path', 'result' => true], + ['config' => 'https://example.com', 'url' => 'https://example.com/a/b/c?test=cat', 'result' => true], + ['config' => 'https://example.com', 'url' => 'https://example.co.uk', 'result' => false], + + // Escapes + ['config' => 'https://(.*?).com', 'url' => 'https://example.com', 'result' => false], + ['config' => 'https://example.com', 'url' => 'https://example.co.uk#https://example.com', 'result' => false], + + // Multi values + ['config' => '*//example.org *//example.com', 'url' => 'https://example.com', 'result' => true], + ['config' => '*//example.org *//example.com', 'url' => 'https://example.com/a/b/c?test=cat#hello', 'result' => true], + ['config' => '*.example.org *.example.com', 'url' => 'https://example.co.uk', 'result' => false], + ['config' => ' *.example.org *.example.com ', 'url' => 'https://example.co.uk', 'result' => false], + ['config' => '* *.example.com', 'url' => 'https://example.co.uk', 'result' => true], + ['config' => '*//example.org *//example.com *//example.co.uk', 'url' => 'https://example.co.uk', 'result' => true], + ['config' => '*//example.org *//example.com *//example.co.uk', 'url' => 'https://example.net', 'result' => false], + + // Further tests + ['config' => 'https://monkey.example.com', 'url' => 'https://monkey.example.com/a/b', 'result' => true,], + ['config' => 'https://monkey.example.com', 'url' => 'https://monkey.example.com/a/b?a=b#ab', 'result' => true,], + ['config' => 'https://monkey.example.com', 'url' => 'https://monkey.example.com:8080/a', 'result' => false,], + ['config' => '*', 'url' => 'https://a.example.com', 'result' => true,], + ['config' => 'https://monkey.example.com', 'url' => 'http://monkey.example.com/a/b?a=b#ab', 'result' => false,], + ['config' => 'https://monkey.example.com', 'url' => 'https://beans.monkey.example.com/a/b?a=b#ab', 'result' => false,], + ['config' => 'https://*monkey.example.com', 'url' => 'https://amonkey.example.com/a/b?a=b#ab', 'result' => true,], + ['config' => 'https://*monkey.example.com', 'url' => 'https://donkey.example.com/a/b/monkey.example.com/b?a=b#ab', 'result' => false,], + ['config' => 'https://monkey.example.com', 'url' => 'https://example.com/monkey.example.com/b?a=monkey.example.com#monkey.example.com', 'result' => false,], + ['config' => 'https://*.example.com', 'url' => 'https://a.b.example.com/a/b', 'result' => true,], + ['config' => 'https://*.example.com', 'url' => 'https://a.b.example.a.com/a/b', 'result' => false,], + ['config' => 'https://*.example.com', 'url' => 'https://a.com/a/b?val=a.example.com', 'result' => false,], + ['config' => 'https://*.example.com', 'url' => 'https://a.com/a/b#example.com', 'result' => false,], + ['config' => 'https://a.*.example.com', 'url' => 'https://a.b.c.example.com/c/d', 'result' => true,], + ['config' => 'https://example.com/webhooks/', 'url' => 'https://example.com/webhooks/beans', 'result' => true,], + ['config' => 'https://example.com/webhooks/', 'url' => 'https://example.com/a/webhooks/', 'result' => false,], + ['config' => 'https://example.com:8080', 'url' => 'https://example.com/a/b', 'result' => false,], + ['config' => 'https://example.com:8080', 'url' => 'https://example.com:8080/a/b', 'result' => true,], + ['config' => 'https://example.com/*', 'url' => 'https://example.com:8080/a/b', 'result' => false,], + ]; + + foreach ($tests as $testCase) { + $validator = new SsrUrlValidator($testCase['config']); + $result = $validator->allowed($testCase['url']); + $this->assertEquals($testCase['result'], $result, "Failed asserting expected result for config {$testCase['config']} and test value {$testCase['url']}"); + } + } + + public function test_wildcard_does_not_match_userinfo_data_but_still_allows_it() + { + $validator = new SsrUrlValidator('https://*monkey.example.com'); + $this->assertFalse($validator->allowed('https://monkey.example.com@a.example.com')); + + $validator = new SsrUrlValidator('https://monkey.example.com*'); + $this->assertFalse($validator->allowed('https://monkey.example.com@a.example.com')); + $this->assertFalse($validator->allowed('https://monkey.example.com:monkey.example.com@a.example.com')); + + $validator = new SsrUrlValidator('https://monkey.example.com'); + $this->assertTrue($validator->allowed('https://a:b@monkey.example.com')); + } + + public function test_percent_encoded_slashes_in_host_are_rejected() + { + $validator = new SsrUrlValidator('*'); + + $this->assertFalse($validator->allowed('https://cat.example.com%2Fa/b')); + $this->assertFalse($validator->allowed('https://cat.example.com%2fa/b')); + $this->assertFalse($validator->allowed('https://cat%2f.example.com/a/b')); + $this->assertFalse($validator->allowed('https://cat.exa%2Fmple.com')); + } +} From ccbeefe674b1db6a7c882f3868f66108a05f7d01 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Tue, 5 May 2026 04:30:37 +0000 Subject: [PATCH 138/204] New Crowdin translations by GitHub Action --- lang/ar/entities.php | 1 + lang/ar/settings.php | 1 + lang/bg/entities.php | 1 + lang/bg/settings.php | 1 + lang/bn/entities.php | 1 + lang/bn/settings.php | 1 + lang/bs/entities.php | 1 + lang/bs/settings.php | 1 + lang/ca/entities.php | 1 + lang/ca/settings.php | 1 + lang/cs/entities.php | 1 + lang/cs/settings.php | 1 + lang/cy/entities.php | 1 + lang/cy/settings.php | 1 + lang/da/entities.php | 1 + lang/da/settings.php | 1 + lang/de/entities.php | 1 + lang/de/settings.php | 1 + lang/de/validation.php | 2 +- lang/de_informal/entities.php | 5 +- lang/de_informal/notifications.php | 4 +- lang/de_informal/settings.php | 3 +- lang/de_informal/validation.php | 2 +- lang/el/entities.php | 1 + lang/el/settings.php | 1 + lang/es/entities.php | 1 + lang/es/settings.php | 1 + lang/es_AR/entities.php | 1 + lang/es_AR/settings.php | 1 + lang/et/entities.php | 1 + lang/et/settings.php | 1 + lang/eu/entities.php | 1 + lang/eu/settings.php | 1 + lang/fa/entities.php | 1 + lang/fa/settings.php | 1 + lang/fi/entities.php | 1 + lang/fi/settings.php | 1 + lang/fr/auth.php | 4 +- lang/fr/entities.php | 1 + lang/fr/settings.php | 1 + lang/he/entities.php | 1 + lang/he/settings.php | 1 + lang/hr/entities.php | 1 + lang/hr/settings.php | 1 + lang/hu/entities.php | 1 + lang/hu/settings.php | 1 + lang/id/entities.php | 1 + lang/id/settings.php | 1 + lang/is/entities.php | 1 + lang/is/settings.php | 1 + lang/it/entities.php | 1 + lang/it/settings.php | 1 + lang/ja/entities.php | 1 + lang/ja/settings.php | 1 + lang/ka/entities.php | 1 + lang/ka/settings.php | 1 + lang/ko/entities.php | 1 + lang/ko/settings.php | 1 + lang/ku/entities.php | 1 + lang/ku/settings.php | 1 + lang/lt/entities.php | 1 + lang/lt/settings.php | 1 + lang/lv/entities.php | 1 + lang/lv/settings.php | 1 + lang/nb/entities.php | 1 + lang/nb/settings.php | 1 + lang/ne/entities.php | 1 + lang/ne/settings.php | 1 + lang/nl/entities.php | 1 + lang/nl/settings.php | 1 + lang/nn/entities.php | 1 + lang/nn/settings.php | 1 + lang/pl/entities.php | 1 + lang/pl/settings.php | 1 + lang/pt/entities.php | 1 + lang/pt/settings.php | 1 + lang/pt_BR/auth.php | 2 +- lang/pt_BR/entities.php | 1 + lang/pt_BR/settings.php | 1 + lang/ro/entities.php | 1 + lang/ro/settings.php | 1 + lang/ru/activities.php | 10 +- lang/ru/common.php | 4 +- lang/ru/editor.php | 6 +- lang/ru/entities.php | 23 +- lang/ru/notifications.php | 2 +- lang/ru/preferences.php | 2 +- lang/ru/settings.php | 25 +- lang/ru/validation.php | 10 +- lang/sk/entities.php | 1 + lang/sk/settings.php | 1 + lang/sl/entities.php | 1 + lang/sl/settings.php | 1 + lang/sq/entities.php | 1 + lang/sq/settings.php | 1 + lang/sr/entities.php | 1 + lang/sr/settings.php | 1 + lang/sv/entities.php | 1 + lang/sv/settings.php | 1 + lang/th/activities.php | 140 +++++++++ lang/th/auth.php | 117 +++++++ lang/th/common.php | 115 +++++++ lang/th/components.php | 46 +++ lang/th/editor.php | 182 +++++++++++ lang/th/entities.php | 477 +++++++++++++++++++++++++++++ lang/th/errors.php | 135 ++++++++ lang/th/notifications.php | 29 ++ lang/th/pagination.php | 12 + lang/th/passwords.php | 15 + lang/th/preferences.php | 52 ++++ lang/th/settings.php | 375 +++++++++++++++++++++++ lang/th/validation.php | 123 ++++++++ lang/tk/entities.php | 1 + lang/tk/settings.php | 1 + lang/tr/entities.php | 1 + lang/tr/settings.php | 1 + lang/uk/entities.php | 1 + lang/uk/settings.php | 1 + lang/uz/entities.php | 1 + lang/uz/settings.php | 1 + lang/vi/entities.php | 1 + lang/vi/settings.php | 1 + lang/zh_CN/entities.php | 1 + lang/zh_CN/settings.php | 1 + lang/zh_TW/entities.php | 1 + lang/zh_TW/settings.php | 1 + 126 files changed, 1970 insertions(+), 50 deletions(-) create mode 100644 lang/th/activities.php create mode 100644 lang/th/auth.php create mode 100644 lang/th/common.php create mode 100644 lang/th/components.php create mode 100644 lang/th/editor.php create mode 100644 lang/th/entities.php create mode 100644 lang/th/errors.php create mode 100644 lang/th/notifications.php create mode 100644 lang/th/pagination.php create mode 100644 lang/th/passwords.php create mode 100644 lang/th/preferences.php create mode 100644 lang/th/settings.php create mode 100644 lang/th/validation.php diff --git a/lang/ar/entities.php b/lang/ar/entities.php index 8b2882c6098..64192a021fd 100644 --- a/lang/ar/entities.php +++ b/lang/ar/entities.php @@ -173,6 +173,7 @@ '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' => 'ترتيب حسب تاريخ الإنشاء', diff --git a/lang/ar/settings.php b/lang/ar/settings.php index 3191bbe3a0a..a1fef8364d8 100644 --- a/lang/ar/settings.php +++ b/lang/ar/settings.php @@ -207,6 +207,7 @@ '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' => 'لم يتم تعيين أي مستخدمين لهذا الدور', diff --git a/lang/bg/entities.php b/lang/bg/entities.php index 42cdad801a9..19e4f518860 100644 --- a/lang/bg/entities.php +++ b/lang/bg/entities.php @@ -173,6 +173,7 @@ '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' => 'Сортирай по дата на създаване', diff --git a/lang/bg/settings.php b/lang/bg/settings.php index a1297e44613..0554100fdd3 100644 --- a/lang/bg/settings.php +++ b/lang/bg/settings.php @@ -207,6 +207,7 @@ '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' => 'В момента няма потребители, назначени за тази роля', diff --git a/lang/bn/entities.php b/lang/bn/entities.php index 74c50be3b2f..5501d2bc229 100644 --- a/lang/bn/entities.php +++ b/lang/bn/entities.php @@ -173,6 +173,7 @@ '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', diff --git a/lang/bn/settings.php b/lang/bn/settings.php index 94ad059d4ce..1bc5d1551ae 100644 --- a/lang/bn/settings.php +++ b/lang/bn/settings.php @@ -207,6 +207,7 @@ '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', diff --git a/lang/bs/entities.php b/lang/bs/entities.php index f671e1b4527..b490c8c9405 100644 --- a/lang/bs/entities.php +++ b/lang/bs/entities.php @@ -173,6 +173,7 @@ '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', diff --git a/lang/bs/settings.php b/lang/bs/settings.php index c4d1eb136eb..3937c650f86 100644 --- a/lang/bs/settings.php +++ b/lang/bs/settings.php @@ -207,6 +207,7 @@ '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', diff --git a/lang/ca/entities.php b/lang/ca/entities.php index 108a38c2c50..edbfdbc4429 100644 --- a/lang/ca/entities.php +++ b/lang/ca/entities.php @@ -173,6 +173,7 @@ '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 «:bookName»', 'books_sort_name' => 'Ordena pel nom', 'books_sort_created' => 'Ordena per la data de creació', diff --git a/lang/ca/settings.php b/lang/ca/settings.php index a890b9809d4..2a2106eb35e 100644 --- a/lang/ca/settings.php +++ b/lang/ca/settings.php @@ -207,6 +207,7 @@ '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', diff --git a/lang/cs/entities.php b/lang/cs/entities.php index d65d85ccb7e..7db62f7bb49 100644 --- a/lang/cs/entities.php +++ b/lang/cs/entities.php @@ -173,6 +173,7 @@ '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' => 'Auto sort option rules can be created in the "Lists & Sorting" settings area by a user with the relevant permissions.', 'books_sort_named' => 'Seřadit knihu :bookName', 'books_sort_name' => 'Seřadit podle názvu', 'books_sort_created' => 'Seřadit podle data vytvoření', diff --git a/lang/cs/settings.php b/lang/cs/settings.php index a8c4036e87e..a7ab9927d8a 100644 --- a/lang/cs/settings.php +++ b/lang/cs/settings.php @@ -207,6 +207,7 @@ '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' => 'Controlled by page delete permissions', 'role_save' => 'Uložit roli', 'role_users' => 'Uživatelé mající tuto roli', 'role_users_none' => 'Žádný uživatel nemá tuto roli', diff --git a/lang/cy/entities.php b/lang/cy/entities.php index e6df4317b1d..af1f6c43c92 100644 --- a/lang/cy/entities.php +++ b/lang/cy/entities.php @@ -173,6 +173,7 @@ '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', diff --git a/lang/cy/settings.php b/lang/cy/settings.php index f4fbf0bba1b..a0519cccf0f 100644 --- a/lang/cy/settings.php +++ b/lang/cy/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Popeth', 'role_own' => 'Meddu', 'role_controlled_by_asset' => 'Wedi\'u rheoli gan yr ased y maent yn cael eu huwchlwytho iddo', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Cadw Rôl', 'role_users' => 'Defnyddwyr yn y rôl hon', 'role_users_none' => 'Nid oes unrhyw ddefnyddwyr wedi’u neilltuo i\'r rôl hon ar hyn o bryd', diff --git a/lang/da/entities.php b/lang/da/entities.php index ecda8a8cf8a..0aa35cc5db4 100644 --- a/lang/da/entities.php +++ b/lang/da/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Flyt kapitler og sider i en bog for at omorganisere dens indhold. Der kan tilføjes andre bøger, som gør det nemt at flytte kapitler og sider mellem bøgerne. Man kan indstille en automatisk sorteringsregel, så bogens indhold automatisk sorteres efter ændringer.', 'books_sort_auto_sort' => 'Mulighed for automatisk sortering', 'books_sort_auto_sort_active' => 'Automatisk sortering Aktiv: :sortName', + 'books_sort_auto_sort_creation_hint' => 'En bruger med de nødvendige rettigheder kan oprette regler for automatisk sortering i indstillingsområdet »Lister og sortering«.', 'books_sort_named' => 'Sorter bog :bookName', 'books_sort_name' => 'Sortér efter navn', 'books_sort_created' => 'Sortér efter oprettelsesdato', diff --git a/lang/da/settings.php b/lang/da/settings.php index 1edf10d0ec9..fb5a1c958ab 100644 --- a/lang/da/settings.php +++ b/lang/da/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Alle', 'role_own' => 'Eget', 'role_controlled_by_asset' => 'Styres af det medie/"asset", de uploades til', + 'role_controlled_by_page_delete' => 'Styres af tilladelser til sletning af sider', 'role_save' => 'Gem rolle', 'role_users' => 'Brugere med denne rolle', 'role_users_none' => 'Ingen brugere er i øjeblikket tildelt denne rolle', diff --git a/lang/de/entities.php b/lang/de/entities.php index 52f9b7acad1..db5d070f444 100644 --- a/lang/de/entities.php +++ b/lang/de/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Verschieben Sie Kapitel und Seiten innerhalb eines Buches, um dessen Inhalt neu zu ordnen. Es können weitere Bücher hinzugefügt werden, wodurch Kapitel und Seiten problemlos zwischen den Büchern verschoben werden können. Optional kann eine automatische Sortierregel festgelegt werden, um den Inhalt dieses Buches bei Änderungen automatisch zu sortieren.', 'books_sort_auto_sort' => 'Automatische Sortierfunktionsoption', 'books_sort_auto_sort_active' => 'Automatische Sortierung aktiv: :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' => 'Buch ":bookName" sortieren', 'books_sort_name' => 'Sortieren nach Namen', 'books_sort_created' => 'Sortieren nach Erstellungsdatum', diff --git a/lang/de/settings.php b/lang/de/settings.php index 64af973a46f..691ee2ee68b 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Alle', 'role_own' => 'Eigene', 'role_controlled_by_asset' => 'Abhängig von dem Asset, in das sie hochgeladen werden', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Rolle speichern', 'role_users' => 'Dieser Rolle zugeordnete Benutzer', 'role_users_none' => 'Derzeit sind diesem Rollentyp keine Benutzer zugewiesen', diff --git a/lang/de/validation.php b/lang/de/validation.php index 21e850bbf9c..40f6e76a13f 100644 --- a/lang/de/validation.php +++ b/lang/de/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'Die Datei konnte nicht hochgeladen werden. Der Server akzeptiert möglicherweise keine Dateien dieser Größe.', 'zip_file' => ':attribute muss eine Datei innerhalb des ZIP referenzieren.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => 'Die Datei :attribute darf :size MB nicht überschreiten.', 'zip_file_mime' => ':attribute muss eine Datei des Typs :validType referenzieren, gefunden :foundType.', 'zip_model_expected' => 'Datenobjekt erwartet, aber ":type" gefunden.', 'zip_unique' => ':attribute muss für den Objekttyp innerhalb des ZIP eindeutig sein.', diff --git a/lang/de_informal/entities.php b/lang/de_informal/entities.php index 16502051cee..708397e7e11 100644 --- a/lang/de_informal/entities.php +++ b/lang/de_informal/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Kapitel und Seiten innerhalb eines Buches verschieben, um dessen Inhalt zu reorganisieren. Andere Bücher können hinzugefügt werden, was das Verschieben von Kapiteln und Seiten zwischen Büchern erleichtert. Optional kann eine automatische Sortierregel erstellt werden, um den Inhalt dieses Buches nach Änderungen automatisch zu sortieren.', 'books_sort_auto_sort' => 'Auto-Sortieroption', 'books_sort_auto_sort_active' => 'Automatische Sortierung aktiv: :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' => 'Buch ":bookName" sortieren', 'books_sort_name' => 'Sortieren nach Namen', 'books_sort_created' => 'Sortieren nach Erstellungsdatum', @@ -410,10 +411,10 @@ 'comment_deleted_success' => 'Kommentar gelöscht', 'comment_created_success' => 'Kommentar hinzugefügt', 'comment_updated_success' => 'Kommentar aktualisiert', - 'comment_archive_success' => 'Kommentar archiviert', + 'comment_archive_success' => 'Kommentar wurde archiviert', 'comment_unarchive_success' => 'Kommentar nicht mehr archiviert', 'comment_view' => 'Kommentar ansehen', - 'comment_jump_to_thread' => 'Zum Thema springen', + 'comment_jump_to_thread' => 'Zu diesem Thema springen', 'comment_delete_confirm' => 'Möchtst du diesen Kommentar wirklich löschen?', 'comment_in_reply_to' => 'Antwort auf :commentId', 'comment_reference' => 'Referenz', diff --git a/lang/de_informal/notifications.php b/lang/de_informal/notifications.php index 4df17078320..dcbe16c9308 100644 --- a/lang/de_informal/notifications.php +++ b/lang/de_informal/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => 'Aktualisierte Seite: :pageName', 'updated_page_intro' => 'Eine Seite wurde in :appName aktualisiert:', 'updated_page_debounce' => 'Um eine Flut von Benachrichtigungen zu vermeiden, wirst du für eine gewisse Zeit keine Benachrichtigungen für weitere Bearbeitungen dieser Seite durch denselben Bearbeiter erhalten.', - 'comment_mention_subject' => 'Sie wurden in einem Kommentar auf der Seite :pageName erwähnt', - 'comment_mention_intro' => 'Sie wurden in einem Kommentar zu :appName: erwähnt', + 'comment_mention_subject' => 'Du wurdest in einem Kommentar auf der Seite :pageName erwähnt', + 'comment_mention_intro' => 'Du wurdest in einem Kommentar zu :appName erwähnt:', 'detail_page_name' => 'Seitenname:', 'detail_page_path' => 'Seitenpfad:', diff --git a/lang/de_informal/settings.php b/lang/de_informal/settings.php index 97ad607d6bc..93ed1902fb5 100644 --- a/lang/de_informal/settings.php +++ b/lang/de_informal/settings.php @@ -76,7 +76,7 @@ 'reg_confirm_restrict_domain_placeholder' => 'Keine Einschränkung gesetzt', // Sorting Settings - 'sorting' => 'Listen & Sortieren', + 'sorting' => 'Listen und Sortierung', 'sorting_book_default' => 'Standardregel für die Sortierung von Büchern', 'sorting_book_default_desc' => 'Wähle die Standard-Sortierregel aus, die auf neue Bücher angewendet werden soll. Dies wirkt sich nicht auf bestehende Bücher aus und kann pro Buch überschrieben werden.', 'sorting_rules' => 'Sortierregeln', @@ -208,6 +208,7 @@ 'role_all' => 'Alle', 'role_own' => 'Eigene', 'role_controlled_by_asset' => 'Berechtigungen werden vom Uploadziel bestimmt', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Rolle speichern', 'role_users' => 'Dieser Rolle zugeordnete Benutzer', 'role_users_none' => 'Bisher sind dieser Rolle keine Benutzer zugeordnet', diff --git a/lang/de_informal/validation.php b/lang/de_informal/validation.php index d693adecd83..5dcbd5102ff 100644 --- a/lang/de_informal/validation.php +++ b/lang/de_informal/validation.php @@ -106,7 +106,7 @@ 'uploaded' => 'Die Datei konnte nicht hochgeladen werden. Der Server akzeptiert möglicherweise keine Dateien dieser Größe.', 'zip_file' => ':attribute muss auf eine Datei innerhalb des ZIP verweisen.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', + 'zip_file_size' => 'Die Datei :attribute darf :size MB nicht überschreiten.', 'zip_file_mime' => ':attribute muss eine Datei des Typs :validType referenzieren, gefunden :foundType.', 'zip_model_expected' => 'Datenobjekt erwartet, aber ":type" gefunden.', 'zip_unique' => ':attribute muss für den Objekttyp innerhalb des ZIP eindeutig sein.', diff --git a/lang/el/entities.php b/lang/el/entities.php index 3779cd18a61..b551d1a86cb 100644 --- a/lang/el/entities.php +++ b/lang/el/entities.php @@ -173,6 +173,7 @@ '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' => 'Ταξινόμηση κατά ημερομηνία δημιουργίας', diff --git a/lang/el/settings.php b/lang/el/settings.php index 6ec5c4fddeb..605b8b40e01 100644 --- a/lang/el/settings.php +++ b/lang/el/settings.php @@ -207,6 +207,7 @@ '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' => 'Σε κανένα χρήστη δεν έχει ανατεθεί αυτήν τη στιγμή αυτός ο ρόλος.', diff --git a/lang/es/entities.php b/lang/es/entities.php index e652d4dc006..a8ec3b10ad2 100644 --- a/lang/es/entities.php +++ b/lang/es/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Mueve capítulos y páginas dentro de un libro para reorganizar su contenido. Se pueden añadir otros libros que permiten mover fácilmente capítulos y páginas entre libros. Opcionalmente, se puede establecer una regla de ordenación automática para ordenar automáticamente el contenido de este libro cuando haya cambios.', 'books_sort_auto_sort' => 'Opción de ordenación automática', 'books_sort_auto_sort_active' => 'Opción de ordenación activa: sortName', + 'books_sort_auto_sort_creation_hint' => 'Las reglas para los ajustes de ordenación automática pueden ser creadas en el área de configuración "Listas y ordenación" por un usuario con los permisos pertinentes.', 'books_sort_named' => 'Organizar libro :bookName', 'books_sort_name' => 'Organizar por Nombre', 'books_sort_created' => 'Organizar por Fecha de creación', diff --git a/lang/es/settings.php b/lang/es/settings.php index bfd3ce1cfe4..516480b6440 100644 --- a/lang/es/settings.php +++ b/lang/es/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Todo', 'role_own' => 'Propio', 'role_controlled_by_asset' => 'Controlado por el contenido al que ha sido subido', + 'role_controlled_by_page_delete' => 'Controlado por página de eliminación de permisos', 'role_save' => 'Guardar rol', 'role_users' => 'Usuarios en este rol', 'role_users_none' => 'No hay usuarios asignados a este rol', diff --git a/lang/es_AR/entities.php b/lang/es_AR/entities.php index 1ae092de1ee..2dc1bd64801 100644 --- a/lang/es_AR/entities.php +++ b/lang/es_AR/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Mueve capítulos y páginas dentro de un libro para reorganizar su contenido. Se pueden añadir otros libros que permiten mover fácilmente capítulos y páginas entre libros. Opcionalmente, se puede establecer una regla de ordenación automática para el contenido de este libro cuando haya cambios.', 'books_sort_auto_sort' => 'Opción de ordenación automática', 'books_sort_auto_sort_active' => 'Opción de ordenación activa: sortName', + 'books_sort_auto_sort_creation_hint' => 'Las reglas para los ajustes de ordenación automática pueden ser creadas en el área de configuración "Listas y ordenación" por un usuario con los permisos pertinentes.', 'books_sort_named' => 'Organizar libro :bookName', 'books_sort_name' => 'Organizar por nombre', 'books_sort_created' => 'Organizar por fecha de creación', diff --git a/lang/es_AR/settings.php b/lang/es_AR/settings.php index 90f43a6f268..3b82d0fb35c 100644 --- a/lang/es_AR/settings.php +++ b/lang/es_AR/settings.php @@ -208,6 +208,7 @@ 'role_all' => 'Todo', 'role_own' => 'Propio', 'role_controlled_by_asset' => 'Controlado por el activo al que ha sido subido', + 'role_controlled_by_page_delete' => 'Controlado por página de eliminación de permisos', 'role_save' => 'Guardar rol', 'role_users' => 'Usuarios en este rol', 'role_users_none' => 'No hay usuarios asignados a este rol', diff --git a/lang/et/entities.php b/lang/et/entities.php index 323985b2d4a..4b7be605ac5 100644 --- a/lang/et/entities.php +++ b/lang/et/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Liiguta raamatu sees peatükke ja lehti, et selle sisu ümber organiseerida. Saad lisada teisi raamatuid, mis võimaldab peatükke ja lehti lihtsasti raamatute vahel liigutada. Lisaks saad määrata automaatse sorteerimise reegli, et selle raamatu sisu muudatuste puhul automaatselt järjestada.', 'books_sort_auto_sort' => 'Automaatne sorteerimine', 'books_sort_auto_sort_active' => 'Automaatne sorteerimine aktiivne: :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' => 'Sorteeri raamat :bookName', 'books_sort_name' => 'Sorteeri nime järgi', 'books_sort_created' => 'Sorteeri loomisaja järgi', diff --git a/lang/et/settings.php b/lang/et/settings.php index bc5a7794e87..fd00da9e3d2 100644 --- a/lang/et/settings.php +++ b/lang/et/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Kõik', 'role_own' => 'Enda omad', 'role_controlled_by_asset' => 'Õigused määratud seotud objekti kaudu', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Salvesta roll', 'role_users' => 'Selle rolliga kasutajad', 'role_users_none' => 'Seda rolli ei ole hetkel ühelgi kasutajal', diff --git a/lang/eu/entities.php b/lang/eu/entities.php index cc51012d50f..4d9e7cb064a 100644 --- a/lang/eu/entities.php +++ b/lang/eu/entities.php @@ -173,6 +173,7 @@ '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' => 'Ordenatu :bookName liburua', 'books_sort_name' => 'Ordenatu izenaren arabera', 'books_sort_created' => 'Ordenatu argitaratze-dataren arabera', diff --git a/lang/eu/settings.php b/lang/eu/settings.php index 0f764dccbd2..dd346d1c9c9 100644 --- a/lang/eu/settings.php +++ b/lang/eu/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Guztiak', 'role_own' => 'Norberarenak', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Gorde rol-a', 'role_users' => 'Rol honetako erabiltzaileak', 'role_users_none' => 'No users are currently assigned to this role', diff --git a/lang/fa/entities.php b/lang/fa/entities.php index d7c0cc1c067..2abcff5768f 100644 --- a/lang/fa/entities.php +++ b/lang/fa/entities.php @@ -173,6 +173,7 @@ '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' => 'مرتب‌سازی بر اساس تاریخ ایجاد', diff --git a/lang/fa/settings.php b/lang/fa/settings.php index 2fa11511838..21ad9624015 100644 --- a/lang/fa/settings.php +++ b/lang/fa/settings.php @@ -207,6 +207,7 @@ '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' => 'در حال حاضر هیچ کاربری به این نقش اختصاص داده نشده است', diff --git a/lang/fi/entities.php b/lang/fi/entities.php index c64621f0d12..dd2ad5e69d3 100644 --- a/lang/fi/entities.php +++ b/lang/fi/entities.php @@ -173,6 +173,7 @@ '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' => 'Järjestä kirja :bookName', 'books_sort_name' => 'Järjestä nimen mukaan', 'books_sort_created' => 'Järjestä luontipäiväyksen mukaan', diff --git a/lang/fi/settings.php b/lang/fi/settings.php index adc47fe2d87..3aec0188670 100644 --- a/lang/fi/settings.php +++ b/lang/fi/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Kaikki', 'role_own' => 'Omat', 'role_controlled_by_asset' => 'Määräytyy sen sisällön mukaan, johon ne on ladattu', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Tallenna rooli', 'role_users' => 'Käyttäjät tässä roolissa', 'role_users_none' => 'Yhtään käyttäjää ei ole osoitettuna tähän rooliin', diff --git a/lang/fr/auth.php b/lang/fr/auth.php index fab11a83bfc..61ca0ee3e60 100644 --- a/lang/fr/auth.php +++ b/lang/fr/auth.php @@ -12,9 +12,9 @@ // Login & Register 'sign_up' => 'S\'inscrire', 'log_in' => 'Se connecter', - 'log_in_with' => 'Se connecter avec :socialDriver', + 'log_in_with' => 'Connexion avec :socialDriver', 'sign_up_with' => 'S\'inscrire avec :socialDriver', - 'logout' => 'Se déconnecter', + 'logout' => 'Déconnexion', 'name' => 'Nom', 'username' => 'Nom d\'utilisateur', diff --git a/lang/fr/entities.php b/lang/fr/entities.php index d650fc83058..fae7480b11c 100644 --- a/lang/fr/entities.php +++ b/lang/fr/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Déplacer les pages et chapitres au sein d’un livre pour en réorganiser le contenu. D’autres livres peuvent être ajoutés pour faciliter le déplacement des pages et chapitres entre les livres. Facultativement, une règle de tri automatique peut être mise en place afin de trier le livre lorsqu’il est édité.', 'books_sort_auto_sort' => 'Option de tri automatique', 'books_sort_auto_sort_active' => 'Tri automatique actif : :sortName', + 'books_sort_auto_sort_creation_hint' => 'Les règles de tri automatiques peuvent être créées dans la section « Listes et tri » des préférences par un utilisateur disposant des autorisations appropriées.', 'books_sort_named' => 'Trier le livre :bookName', 'books_sort_name' => 'Trier par le nom', 'books_sort_created' => 'Trier par la date de création', diff --git a/lang/fr/settings.php b/lang/fr/settings.php index 8c6c57f33aa..664184a7d98 100644 --- a/lang/fr/settings.php +++ b/lang/fr/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Tous', 'role_own' => 'Propres', 'role_controlled_by_asset' => 'Contrôlé par les ressources les ayant envoyés', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Enregistrer le rôle', 'role_users' => 'Utilisateurs ayant ce rôle', 'role_users_none' => 'Aucun utilisateur avec ce rôle actuellement', diff --git a/lang/he/entities.php b/lang/he/entities.php index d8b264d8b68..38ffc95cff0 100644 --- a/lang/he/entities.php +++ b/lang/he/entities.php @@ -173,6 +173,7 @@ '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' => 'מיין לפי תאריך יצירה', diff --git a/lang/he/settings.php b/lang/he/settings.php index 46150081aa6..b8f20481328 100644 --- a/lang/he/settings.php +++ b/lang/he/settings.php @@ -207,6 +207,7 @@ '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' => 'אין משתמשים המשוייכים לתפקיד זה', diff --git a/lang/hr/entities.php b/lang/hr/entities.php index 99df50ea33c..ff51c1b2f6f 100644 --- a/lang/hr/entities.php +++ b/lang/hr/entities.php @@ -173,6 +173,7 @@ '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' => 'Razvrstaj knjigu :bookName', 'books_sort_name' => 'Razvrstaj po imenu', 'books_sort_created' => 'Razvrstaj po datumu nastanka', diff --git a/lang/hr/settings.php b/lang/hr/settings.php index 0692c8d7a13..60b2485efb1 100644 --- a/lang/hr/settings.php +++ b/lang/hr/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Sve', 'role_own' => 'Vlastito', 'role_controlled_by_asset' => 'Kontrolirano od strane vlasnika', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Spremi ulogu', 'role_users' => 'Korisnici u ovoj ulozi', 'role_users_none' => 'Trenutno nijedan korisnik nije u ovoj ulozi', diff --git a/lang/hu/entities.php b/lang/hu/entities.php index 570120dfa99..707de62d871 100644 --- a/lang/hu/entities.php +++ b/lang/hu/entities.php @@ -173,6 +173,7 @@ '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 könyv rendezése', 'books_sort_name' => 'Rendezés név szerint', 'books_sort_created' => 'Rendezés létrehozás dátuma szerint', diff --git a/lang/hu/settings.php b/lang/hu/settings.php index 53b1cdcc424..aaccbd35f4c 100644 --- a/lang/hu/settings.php +++ b/lang/hu/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Összes', 'role_own' => 'Saját', 'role_controlled_by_asset' => 'Az általuk feltöltött eszköz által ellenőrzött', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Szerepkör mentése', 'role_users' => 'Felhasználók ebben a szerepkörben', 'role_users_none' => 'Jelenleg nincsenek felhasználók hozzárendelve ehhez a szerepkörhöz', diff --git a/lang/id/entities.php b/lang/id/entities.php index 2f932992571..d978a3d19b1 100644 --- a/lang/id/entities.php +++ b/lang/id/entities.php @@ -173,6 +173,7 @@ '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' => 'Sortir Buku :bookName', 'books_sort_name' => 'Diurutkan berdasarkan nama', 'books_sort_created' => 'Urutkan berdasarkan Tanggal Dibuat', diff --git a/lang/id/settings.php b/lang/id/settings.php index 8bdd99e6890..fe3289ffa46 100644 --- a/lang/id/settings.php +++ b/lang/id/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Semua', 'role_own' => 'Sendiri', 'role_controlled_by_asset' => 'Dikendalikan oleh aset tempat mereka diunggah', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Simpan Peran', 'role_users' => 'Peran berhasil diperbarui', 'role_users_none' => 'Saat ini tidak ada pengguna yang ditugaskan untuk peran ini', diff --git a/lang/is/entities.php b/lang/is/entities.php index 4308835c7d8..0d9a6fc1eb7 100644 --- a/lang/is/entities.php +++ b/lang/is/entities.php @@ -173,6 +173,7 @@ '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' => 'Raða bók :bookName', 'books_sort_name' => 'Raða eftir nafni', 'books_sort_created' => 'Raða eftir skráningar dagsetningu', diff --git a/lang/is/settings.php b/lang/is/settings.php index b1f21ac10b8..877e0dfa8ce 100644 --- a/lang/is/settings.php +++ b/lang/is/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Allt', 'role_own' => 'Eigin', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Vista hlutverk', 'role_users' => 'Notendur í þessu hlutverki', 'role_users_none' => 'Engir notendur eru eins og er í þessu hlutverki', diff --git a/lang/it/entities.php b/lang/it/entities.php index eb9868973e5..1cce5fcd54f 100644 --- a/lang/it/entities.php +++ b/lang/it/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Spostare i capitoli e le pagine di un libro per riorganizzarne il contenuto. Possono essere aggiunti altri libri che permettono di spostare facilmente capitoli e pagine tra i libri. Opzionalmente una regola di ordinamento automatico può essere impostata per ordinare automaticamente i contenuti di questo libro in caso di modifiche.', 'books_sort_auto_sort' => 'Opzione Ordinamento Automatico', 'books_sort_auto_sort_active' => 'Ordinamento Automatico Attivo: :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' => 'Ordina il libro :bookName', 'books_sort_name' => 'Ordina per Nome', 'books_sort_created' => 'Ordina per Data di creazione', diff --git a/lang/it/settings.php b/lang/it/settings.php index 2b5819b2aa7..9a272db33d9 100644 --- a/lang/it/settings.php +++ b/lang/it/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Tutti', 'role_own' => 'Propri', 'role_controlled_by_asset' => 'Controllato dall\'entità in cui sono caricati', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Salva ruolo', 'role_users' => 'Utenti in questo ruolo', 'role_users_none' => 'Nessun utente assegnato a questo ruolo', diff --git a/lang/ja/entities.php b/lang/ja/entities.php index 551a2e49672..57703997f3f 100644 --- a/lang/ja/entities.php +++ b/lang/ja/entities.php @@ -173,6 +173,7 @@ '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' => '作成日で並べ替え', diff --git a/lang/ja/settings.php b/lang/ja/settings.php index 378e3a7748e..a3cbf696a18 100644 --- a/lang/ja/settings.php +++ b/lang/ja/settings.php @@ -207,6 +207,7 @@ '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' => 'この役割が付与されたユーザーはいません', diff --git a/lang/ka/entities.php b/lang/ka/entities.php index 74c50be3b2f..5501d2bc229 100644 --- a/lang/ka/entities.php +++ b/lang/ka/entities.php @@ -173,6 +173,7 @@ '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', diff --git a/lang/ka/settings.php b/lang/ka/settings.php index c4d1eb136eb..3937c650f86 100644 --- a/lang/ka/settings.php +++ b/lang/ka/settings.php @@ -207,6 +207,7 @@ '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', diff --git a/lang/ko/entities.php b/lang/ko/entities.php index 44809d99ff4..c54d6c7dc05 100644 --- a/lang/ko/entities.php +++ b/lang/ko/entities.php @@ -173,6 +173,7 @@ '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' => '만든 날짜', diff --git a/lang/ko/settings.php b/lang/ko/settings.php index 97af673c6af..9aabe5c2a81 100644 --- a/lang/ko/settings.php +++ b/lang/ko/settings.php @@ -207,6 +207,7 @@ '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' => '역할이 부여된 사용자가 없습니다.', diff --git a/lang/ku/entities.php b/lang/ku/entities.php index 74c50be3b2f..5501d2bc229 100644 --- a/lang/ku/entities.php +++ b/lang/ku/entities.php @@ -173,6 +173,7 @@ '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', diff --git a/lang/ku/settings.php b/lang/ku/settings.php index c4d1eb136eb..3937c650f86 100644 --- a/lang/ku/settings.php +++ b/lang/ku/settings.php @@ -207,6 +207,7 @@ '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', diff --git a/lang/lt/entities.php b/lang/lt/entities.php index 6c4472d85a7..f6610a22a7d 100644 --- a/lang/lt/entities.php +++ b/lang/lt/entities.php @@ -173,6 +173,7 @@ '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' => 'Rūšiuoti knygą :bookName', 'books_sort_name' => 'Rūšiuoti pagal vardą', 'books_sort_created' => 'Rūšiuoti pagal sukūrimo datą', diff --git a/lang/lt/settings.php b/lang/lt/settings.php index f797e567ec1..23dd38d50e6 100644 --- a/lang/lt/settings.php +++ b/lang/lt/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Visi', 'role_own' => 'Nuosavi', 'role_controlled_by_asset' => 'Kontroliuojami nuosavybės, į kurią yra įkelti', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Išsaugoti vaidmenį', 'role_users' => 'Naudotojai šiame vaidmenyje', 'role_users_none' => 'Šiuo metu prie šio vaidmens nėra priskirta naudotojų', diff --git a/lang/lv/entities.php b/lang/lv/entities.php index 5f921048e52..f0f32ab2174 100644 --- a/lang/lv/entities.php +++ b/lang/lv/entities.php @@ -173,6 +173,7 @@ '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' => 'Kārtot grāmatu :bookName', 'books_sort_name' => 'Kārtot pēc nosaukuma', 'books_sort_created' => 'Kārtot pēc izveidošanas datuma', diff --git a/lang/lv/settings.php b/lang/lv/settings.php index 9dc6bf402bd..0e11ebd65d6 100644 --- a/lang/lv/settings.php +++ b/lang/lv/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Visi', 'role_own' => 'Savi', 'role_controlled_by_asset' => 'Kontrolē resurss, uz ko tie ir augšupielādēti', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Saglabāt grupu', 'role_users' => 'Lietotāji šajā grupā', 'role_users_none' => 'Pagaidām neviens lietotājs nav pievienots šai grupai', diff --git a/lang/nb/entities.php b/lang/nb/entities.php index cf0fdffded0..a67c35e1748 100644 --- a/lang/nb/entities.php +++ b/lang/nb/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Flytt kapitler og sider innen en bok for å reorganisere innholdet. Andre bøker kan legges til, noe som gjør det enkelt å flytte kapitler og sider mellom bøkene. Valgfritt kan en automatisk sorteringsregel settes for å automatisk sortere innholdet i denne boken ved endringer.', 'books_sort_auto_sort' => 'Automatisk sorteringsalternativ', 'books_sort_auto_sort_active' => 'Automatisk sortering aktiv: :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' => 'Omorganisér :bookName (bok)', 'books_sort_name' => 'Sorter på navn', 'books_sort_created' => 'Sorter på opprettet dato', diff --git a/lang/nb/settings.php b/lang/nb/settings.php index 5fcaaaca6c1..5e779beead5 100644 --- a/lang/nb/settings.php +++ b/lang/nb/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Alle', 'role_own' => 'Egne', 'role_controlled_by_asset' => 'Kontrollert av eiendelen de er lastet opp til', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Lagre rolle', 'role_users' => 'Kontoholdere med denne rollen', 'role_users_none' => 'Ingen kontoholdere er gitt denne rollen', diff --git a/lang/ne/entities.php b/lang/ne/entities.php index 88b202deed3..4d9f78ea504 100644 --- a/lang/ne/entities.php +++ b/lang/ne/entities.php @@ -173,6 +173,7 @@ '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' => 'सिर्जना मितिअनुसार वर्गीकृत गर्नुहोस्', diff --git a/lang/ne/settings.php b/lang/ne/settings.php index dbc7d8e9fc9..f52bcd42026 100644 --- a/lang/ne/settings.php +++ b/lang/ne/settings.php @@ -207,6 +207,7 @@ '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' => 'यो भूमिकामा हाल कुनै प्रयोगकर्ता छैन', diff --git a/lang/nl/entities.php b/lang/nl/entities.php index b9be1f42b3c..c39beef2de6 100644 --- a/lang/nl/entities.php +++ b/lang/nl/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Verplaats hoofdstukken en pagina\'s door het boek om ze te organiseren. Andere boeken kunnen worden toegevoegd zodat hoofdstukken en pagina\'s gemakkelijk tussen boeken kunnen worden verplaatst. Het is mogelijk om een automatische sorteerregel in te stellen die de inhoud zal sorteren bij wijzigingen.', 'books_sort_auto_sort' => 'Automatisch Sorteren', 'books_sort_auto_sort_active' => 'Automatisch Sorteren Actief: :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' => 'Sorteer boek :bookName', 'books_sort_name' => 'Sorteren op naam', 'books_sort_created' => 'Sorteren op datum van aanmaken', diff --git a/lang/nl/settings.php b/lang/nl/settings.php index c8d071119ff..b82c52debba 100644 --- a/lang/nl/settings.php +++ b/lang/nl/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Alles', 'role_own' => 'Eigen', 'role_controlled_by_asset' => 'Gecontroleerd door de asset waar deze is geüpload', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Rol Opslaan', 'role_users' => 'Gebruikers in deze rol', 'role_users_none' => 'Geen enkele gebruiker heeft deze rol', diff --git a/lang/nn/entities.php b/lang/nn/entities.php index 4c75d501986..4f79f3d7518 100644 --- a/lang/nn/entities.php +++ b/lang/nn/entities.php @@ -173,6 +173,7 @@ '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' => 'Omorganiser :bookName', 'books_sort_name' => 'Sorter på namn', 'books_sort_created' => 'Sorter på oppretta dato', diff --git a/lang/nn/settings.php b/lang/nn/settings.php index e4b6e6af93d..b6c322c9c34 100644 --- a/lang/nn/settings.php +++ b/lang/nn/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Alle', 'role_own' => 'Egne', 'role_controlled_by_asset' => 'Kontrollert av eiendelen de er lastet opp til', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Lagre rolle', 'role_users' => 'Kontoholdere med denne rollen', 'role_users_none' => 'Ingen kontoholdere er gitt denne rollen', diff --git a/lang/pl/entities.php b/lang/pl/entities.php index f3ad807af29..88e4344de40 100644 --- a/lang/pl/entities.php +++ b/lang/pl/entities.php @@ -173,6 +173,7 @@ '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' => 'Opcja automatycznego sortowania', 'books_sort_auto_sort_active' => 'Automatyczne sortowanie aktywne: :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' => 'Sortuj książkę :bookName', 'books_sort_name' => 'Sortuj według nazwy', 'books_sort_created' => 'Sortuj według daty utworzenia', diff --git a/lang/pl/settings.php b/lang/pl/settings.php index 775d4f25d2c..98201406e7b 100644 --- a/lang/pl/settings.php +++ b/lang/pl/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Wszyscy', 'role_own' => 'Własne', 'role_controlled_by_asset' => 'Kontrolowane przez zasób, do którego zostały udostępnione', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Zapisz rolę', 'role_users' => 'Użytkownicy w tej roli', 'role_users_none' => 'Brak użytkowników zapisanych do tej roli', diff --git a/lang/pt/entities.php b/lang/pt/entities.php index e882c79d001..709b8466aa7 100644 --- a/lang/pt/entities.php +++ b/lang/pt/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Mova capítulos e páginas de um livro para reorganizar o seu conteúdo. É possível acrescentar outros livros, o que permite uma movimentação fácil de capítulos e páginas entre livros. Opcionalmente, uma regra de organização automática pode ser definida para classificar automaticamente o conteúdo deste livro após alterações.', 'books_sort_auto_sort' => '', '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' => 'Ordenar Livro :bookName', 'books_sort_name' => 'Ordenar por Nome', 'books_sort_created' => 'Ordenar por Data de Criação', diff --git a/lang/pt/settings.php b/lang/pt/settings.php index a59335b7cee..47fecbcbfee 100644 --- a/lang/pt/settings.php +++ b/lang/pt/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Todos', 'role_own' => 'Próprio', 'role_controlled_by_asset' => 'Controlado pelo ativo para o qual eles são enviados', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Guardar Cargo', 'role_users' => 'Utilizadores com este cargo', 'role_users_none' => 'Nenhum utilizador está atualmente vinculado a este cargo', diff --git a/lang/pt_BR/auth.php b/lang/pt_BR/auth.php index b5fd974aa20..0580542b49b 100644 --- a/lang/pt_BR/auth.php +++ b/lang/pt_BR/auth.php @@ -89,7 +89,7 @@ 'mfa_setup_action' => 'Configurações', 'mfa_backup_codes_usage_limit_warning' => 'Você tem menos de 5 códigos de backup restantes, Por favor, gere e armazene um novo conjunto antes de esgotar suas opções de códigos de backup para evitar estar bloqueado para fora da sua conta.', 'mfa_option_totp_title' => 'Aplicativo Móvel', - 'mfa_option_totp_desc' => 'Para usar a autenticação multi-fator, você precisará de um aplicativo móvel que suporte TOTP como o Google Authenticator, Authy ou o Microsoft Authenticator.', + 'mfa_option_totp_desc' => 'Para usar a autenticação multi-fator, você precisará de um aplicativo móvel que suporte TOTP como o Google Authenticator, Authy, Microsoft Authenticator ou Proton Authenticator.', 'mfa_option_backup_codes_title' => 'Códigos de backup', 'mfa_option_backup_codes_desc' => 'Gera um conjunto de códigos de backup de uso único que você inserirá no login para verificar sua identidade. Certifique-se de armazená-los em um local seguro e protegido.', 'mfa_gen_confirm_and_enable' => 'Confirmar e habilitar', diff --git a/lang/pt_BR/entities.php b/lang/pt_BR/entities.php index 5a948c8dcd8..0211f45fa7c 100644 --- a/lang/pt_BR/entities.php +++ b/lang/pt_BR/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Mova capítulos e páginas de um livro para reorganizar seu conteúdo. É possível acrescentar outros livros, o que permite uma movimentação fácil de capítulos e páginas entre livros. Opcionalmente, uma regra de ordenação automática pode ser definida para ordenar automaticamente o conteúdo deste livro após alterações.', 'books_sort_auto_sort' => 'Opção de ordenação automática', 'books_sort_auto_sort_active' => 'Ordenação automática ativa: :sortName', + 'books_sort_auto_sort_creation_hint' => 'Regras de ordenação automática podem ser criadas na área de configurações "Lista e Classificação" por um usuário com as permissões relevantes.', 'books_sort_named' => 'Ordenar Livro :bookName', 'books_sort_name' => 'Ordernar por Nome', 'books_sort_created' => 'Ordenar por Data de Criação', diff --git a/lang/pt_BR/settings.php b/lang/pt_BR/settings.php index 97b434727f8..9b7c6a7d4f9 100644 --- a/lang/pt_BR/settings.php +++ b/lang/pt_BR/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Todos', 'role_own' => 'Próprio', 'role_controlled_by_asset' => 'Controlado pelos ativos nos quais o upload foi realizado', + 'role_controlled_by_page_delete' => 'Controlado pelas permissões de exclusão de página', 'role_save' => 'Salvar Perfil', 'role_users' => 'Usuários com este perfil', 'role_users_none' => 'Nenhum usuário está atualmente vinculado a este perfil', diff --git a/lang/ro/entities.php b/lang/ro/entities.php index ac8c9d32609..aa18d09fba7 100644 --- a/lang/ro/entities.php +++ b/lang/ro/entities.php @@ -173,6 +173,7 @@ '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' => 'Sortează cartea :bookName', 'books_sort_name' => 'Sortează după nume', 'books_sort_created' => 'Sortează după data creării', diff --git a/lang/ro/settings.php b/lang/ro/settings.php index d65a8e0714f..117c4ca611a 100644 --- a/lang/ro/settings.php +++ b/lang/ro/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Tot', 'role_own' => 'Propriu', 'role_controlled_by_asset' => 'Controlat de activele pe care sunt încărcate', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Salvare rol', 'role_users' => 'Utilizatori cu acest rol', 'role_users_none' => 'Nici un utilizator nu este asociat acestui rol', diff --git a/lang/ru/activities.php b/lang/ru/activities.php index d353b7a7640..d9ac1685ba9 100644 --- a/lang/ru/activities.php +++ b/lang/ru/activities.php @@ -85,12 +85,12 @@ 'webhook_delete_notification' => 'Вебхук успешно удален', // Imports - 'import_create' => 'created import', - 'import_create_notification' => 'Import successfully uploaded', + 'import_create' => 'создал импорт', + 'import_create_notification' => 'Импорт успешно добавлен', 'import_run' => 'обновлен импорт', - 'import_run_notification' => 'Content successfully imported', - 'import_delete' => 'deleted import', - 'import_delete_notification' => 'Import successfully deleted', + 'import_run_notification' => 'Контент успешно импортирован', + 'import_delete' => 'удалил импорт', + 'import_delete_notification' => 'Импорт успешно удален', // Users 'user_create' => 'создал пользователя', diff --git a/lang/ru/common.php b/lang/ru/common.php index fa9969c32af..bf77a999df3 100644 --- a/lang/ru/common.php +++ b/lang/ru/common.php @@ -30,8 +30,8 @@ 'create' => 'Создание', 'update' => 'Обновление', 'edit' => 'Редактировать', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'Архивировать', + 'unarchive' => 'Вернуть из архива', 'sort' => 'Сортировать', 'move' => 'Переместить', 'copy' => 'Скопировать', diff --git a/lang/ru/editor.php b/lang/ru/editor.php index d26f7edfe08..da661559bd9 100644 --- a/lang/ru/editor.php +++ b/lang/ru/editor.php @@ -149,7 +149,7 @@ 'url' => 'URL-адрес', 'text_to_display' => 'Текст для отображения', 'title' => 'Заголовок', - 'browse_links' => 'Browse links', + 'browse_links' => 'Просмотр ссылки', 'open_link' => 'Открыть ссылку', 'open_link_in' => 'Открыть ссылку в...', 'open_link_current' => 'В текущем окне', @@ -166,8 +166,8 @@ 'about' => 'О редакторе', 'about_title' => 'О редакторе WYSIWYG', 'editor_license' => 'Лицензия редактора и авторские права', - 'editor_lexical_license' => 'This editor is built as a fork of :lexicalLink which is distributed under the MIT license.', - 'editor_lexical_license_link' => 'Full license details can be found here.', + 'editor_lexical_license' => 'Этот редактор создан с помощью :lexicalLink, распространяемый под лицензией MIT.', + 'editor_lexical_license_link' => 'Здесь вы можете найти полную информацию о лицензии.', 'editor_tiny_license' => 'Этот редактор собран с помощью :tinyLink, который предоставляется под MIT лицензией.', 'editor_tiny_license_link' => 'Авторские права и подробности лицензии TinyMCE вы можете найти здесь.', 'save_continue' => 'Сохранить страницу и продолжить', diff --git a/lang/ru/entities.php b/lang/ru/entities.php index bf224c10a91..28d096fe802 100644 --- a/lang/ru/entities.php +++ b/lang/ru/entities.php @@ -51,18 +51,18 @@ 'import_pending' => 'Ожидается импорт', 'import_pending_none' => 'Импорт не был запущен.', 'import_continue' => 'Продолжить импорт', - 'import_continue_desc' => 'Review the content due to be imported from the uploaded ZIP file. When ready, run the import to add its contents to this system. The uploaded ZIP import file will be automatically removed on successful import.', - 'import_details' => 'Import Details', + 'import_continue_desc' => 'Проверьте содержимое, которое должно быть импортировано из загруженного ZIP-файла. Если все готово, выполните импорт, чтобы добавить его содержимое в эту систему. Загруженный ZIP файл будет автоматически удален при успешном импорте.', + 'import_details' => 'Детали загрузки', 'import_run' => 'Запустить импорт', 'import_size' => ':size Import ZIP Size', - 'import_uploaded_at' => 'Uploaded :relativeTime', - 'import_uploaded_by' => 'Uploaded by', + 'import_uploaded_at' => 'Загружено :relativeTime', + 'import_uploaded_by' => 'Загружено пользователем', 'import_location' => 'Import Location', - 'import_location_desc' => 'Select a target location for your imported content. You\'ll need the relevant permissions to create within the location you choose.', + 'import_location_desc' => 'Выберите целевое местоположение для импортированного содержимого. Для создания в выбранном месте необходимы соответствующие разрешения.', 'import_delete_confirm' => 'Are you sure you want to delete this import?', - 'import_delete_desc' => 'This will delete the uploaded import ZIP file, and cannot be undone.', + 'import_delete_desc' => 'Это приведет к удалению загруженного ZIP файла и не может быть отменено.', 'import_errors' => 'Ошибки импорта', - 'import_errors_desc' => 'The follow errors occurred during the import attempt:', + 'import_errors_desc' => 'Во время попытки импорта произошла следующая ошибка:', 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter', 'breadcrumb_siblings_for_book' => 'Navigate siblings for book', @@ -170,9 +170,10 @@ 'books_search_this' => 'Поиск в этой книге', 'books_navigation' => 'Навигация по книге', 'books_sort' => 'Сортировка содержимого книги', - '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_desc' => 'Переместите разделы и страницы в книге, чтобы изменить содержание книги. Могут быть добавлены другие книги, что позволяет легко перемещать разделы и страницы между книгами. При желании правило автоматической сортировки может быть установлено для автоматической сортировки содержимого этой книги после изменений.', + 'books_sort_auto_sort' => 'Автосортировка', + 'books_sort_auto_sort_active' => 'Автосортировка активна: :sortName', + 'books_sort_auto_sort_creation_hint' => 'Правила автоматической сортировки могут быть созданы в области настроек «Список и Сортировка» пользователем с соответствующими разрешениями.', 'books_sort_named' => 'Сортировка книги :bookName', 'books_sort_name' => 'По имени', 'books_sort_created' => 'По дате создания', @@ -251,7 +252,7 @@ 'pages_edit_switch_to_markdown_clean' => 'Только Markdown (с возможными потерями форматирования)', 'pages_edit_switch_to_markdown_stable' => 'Полное сохранение форматирования (HTML)', 'pages_edit_switch_to_wysiwyg' => 'Переключиться в WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg' => 'Switch to new WYSIWYG', + 'pages_edit_switch_to_new_wysiwyg' => 'Переключиться на новый WYSIWYG', 'pages_edit_switch_to_new_wysiwyg_desc' => '(В бета-тестировании)', 'pages_edit_set_changelog' => 'Задать список изменений', 'pages_edit_enter_changelog_desc' => 'Введите краткое описание внесенных изменений', diff --git a/lang/ru/notifications.php b/lang/ru/notifications.php index 96e853723b7..ea885d910b9 100644 --- a/lang/ru/notifications.php +++ b/lang/ru/notifications.php @@ -12,7 +12,7 @@ 'updated_page_intro' => 'Страница была обновлена в :appName:', 'updated_page_debounce' => 'Чтобы предотвратить массовые уведомления, в течение некоторого времени вы не будете получать уведомления о дальнейших правках этой страницы этим же редактором.', 'comment_mention_subject' => 'Вы были упомянуты в комментарии на странице: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_intro' => 'Вы были упомянуты в комментариях к :appName:', 'detail_page_name' => 'Имя страницы:', 'detail_page_path' => 'Путь страницы:', diff --git a/lang/ru/preferences.php b/lang/ru/preferences.php index b61b252c8dc..2dcd20b11bb 100644 --- a/lang/ru/preferences.php +++ b/lang/ru/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'Управляйте полученными по электронной почте уведомлениями при выполнении определенных действий в системе.', 'notifications_opt_own_page_changes' => 'Уведомлять об изменениях в собственных страницах', 'notifications_opt_own_page_comments' => 'Уведомлять о комментариях на собственных страницах', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'Уведомлять, когда меня упоминали в комментарии', 'notifications_opt_comment_replies' => 'Уведомлять об ответах на мои комментарии', 'notifications_save' => 'Сохранить настройки', 'notifications_update_success' => 'Настройки уведомлений были обновлены!', diff --git a/lang/ru/settings.php b/lang/ru/settings.php index 76a2eebbf1e..7bf5832a369 100644 --- a/lang/ru/settings.php +++ b/lang/ru/settings.php @@ -82,28 +82,28 @@ 'sorting_rules_desc' => 'Выберите правило сортировки по умолчанию для новых книг. Это не повлияет на существующие книги и может быть изменено для каждой книги отдельно.', 'sort_rule_assigned_to_x_books' => 'Используется в :count книгах', 'sort_rule_create' => 'Создать правило сортировки', - 'sort_rule_edit' => 'Edit Sort Rule', + 'sort_rule_edit' => 'Изменить правило сортировки', 'sort_rule_delete' => 'Удалить правило сортировки', 'sort_rule_delete_desc' => 'Удалить это правило сортировки из системы. Книги, использующие эту сортировку, вернутся к ручной сортировке.', - 'sort_rule_delete_warn_books' => 'This sort rule is currently used on :count book(s). Are you sure you want to delete this?', + 'sort_rule_delete_warn_books' => 'Это правило сортировки в настоящее время используется в :count book(s). Вы уверены, что хотите удалить его?', 'sort_rule_delete_warn_default' => 'Это правило сортировки используется по умолчанию для книг. Вы уверены, что хотите удалить его?', 'sort_rule_details' => 'Детали правила сортировки', - 'sort_rule_details_desc' => 'Set a name for this sort rule, which will appear in lists when users are selecting a sort.', + 'sort_rule_details_desc' => 'Задайте имя для этого правила сортировки, оно будет отображаться в списках, когда пользователи выбирают сортировку.', 'sort_rule_operations' => 'Sort Operations', - 'sort_rule_operations_desc' => 'Configure the sort actions to be performed by moving them from the list of available operations. Upon use, the operations will be applied in order, from top to bottom. Any changes made here will be applied to all assigned books upon save.', + 'sort_rule_operations_desc' => 'Настройка сортировки выполняемых действий путем перемещения их из списка доступных операций. После использования операции будут выполняться по порядку сверху вниз. Любые изменения, внесенные здесь, будут применены ко всем назначенным книгам после сохранения.', 'sort_rule_available_operations' => 'Доступные операции', - 'sort_rule_available_operations_empty' => 'No operations remaining', + 'sort_rule_available_operations_empty' => 'Операций не осталось', 'sort_rule_configured_operations' => 'Configured Operations', 'sort_rule_configured_operations_empty' => 'Перетащите/добавьте операции из списка "Доступные операции"', 'sort_rule_op_asc' => '(Возрастание)', 'sort_rule_op_desc' => '(Убывание)', - 'sort_rule_op_name' => 'Name - Alphabetical', + 'sort_rule_op_name' => 'Имя - по алфавиту', 'sort_rule_op_name_numeric' => 'По нумерации', - 'sort_rule_op_created_date' => 'Created Date', - 'sort_rule_op_updated_date' => 'Updated Date', + 'sort_rule_op_created_date' => 'По дате создания', + 'sort_rule_op_updated_date' => 'По дате обновления', 'sort_rule_op_chapters_first' => 'Главы в начале', 'sort_rule_op_chapters_last' => 'Главы в конце', - 'sorting_page_limits' => 'Per-Page Display Limits', + 'sorting_page_limits' => 'Ограничения показа на странице', 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', // Maintenance settings @@ -194,19 +194,20 @@ 'role_access_api' => 'Доступ к системному API', 'role_manage_settings' => 'Управление настройками приложения', 'role_export_content' => 'Экспорт контента', - 'role_import_content' => 'Import content', + 'role_import_content' => 'Импортировать содержимое', 'role_editor_change' => 'Изменение редактора страниц', 'role_notifications' => 'Получение и управление уведомлениями', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_permission_note_users_and_roles' => 'Эти разрешения также обеспечивают видимость и поиск пользователей и ролей в системе.', 'role_asset' => 'Права доступа к материалам', 'roles_system_warning' => 'Имейте в виду, что доступ к любому из указанных выше трех разрешений может позволить пользователю изменить свои собственные привилегии или привилегии других пользователей системы. Назначать роли с этими правами можно только доверенным пользователям.', 'role_asset_desc' => 'Эти разрешения контролируют доступ по умолчанию к параметрам внутри системы. Разрешения на книги, главы и страницы перезапишут эти разрешения.', 'role_asset_admins' => 'Администраторы автоматически получают доступ ко всему контенту, но эти опции могут отображать или скрывать параметры пользовательского интерфейса.', 'role_asset_image_view_note' => 'Это относится к видимости в менеджере изображений. Фактический доступ к загруженным файлам изображений будет зависеть от опции хранения системных изображений.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Эти разрешения также обеспечивают видимость и поиск пользователей в системе.', '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' => 'Нет пользователей с данной ролью', diff --git a/lang/ru/validation.php b/lang/ru/validation.php index ce94faa3b84..6d011cff10c 100644 --- a/lang/ru/validation.php +++ b/lang/ru/validation.php @@ -105,11 +105,11 @@ 'url' => 'Формат :attribute некорректен.', 'uploaded' => 'Не удалось загрузить файл. Сервер не может принимать файлы такого размера.', - 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', - 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', - 'zip_model_expected' => 'Data object expected but ":type" found.', - 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', + 'zip_file' => ':attribute должен ссылаться на файл внутри ZIP.', + 'zip_file_size' => 'Файл :attribute не должен превышать :size МБ.', + 'zip_file_mime' => ':attribute должен ссылаться на файл типа :validTypes, найден :foundType.', + 'zip_model_expected' => 'Ожидался объект данных, но найдено ":type".', + 'zip_unique' => 'Значение :attribute должно быть уникальным для типа объекта внутри ZIP.', // Custom validation lines 'custom' => [ diff --git a/lang/sk/entities.php b/lang/sk/entities.php index eb52545357c..48b662bf388 100644 --- a/lang/sk/entities.php +++ b/lang/sk/entities.php @@ -173,6 +173,7 @@ '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' => 'Zoradiť knihu :bookName', 'books_sort_name' => 'Zoradiť podľa mena', 'books_sort_created' => 'Zoradiť podľa dátumu vytvorenia', diff --git a/lang/sk/settings.php b/lang/sk/settings.php index 67671f6f82c..e18801ff467 100644 --- a/lang/sk/settings.php +++ b/lang/sk/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Všetko', 'role_own' => 'Vlastné', 'role_controlled_by_asset' => 'Regulované zdrojom, do ktorého sú nahrané', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Uložiť rolu', 'role_users' => 'Používatelia s touto rolou', 'role_users_none' => 'Žiadni používatelia nemajú priradenú túto rolu', diff --git a/lang/sl/entities.php b/lang/sl/entities.php index 00ac991811e..86a43132ef6 100644 --- a/lang/sl/entities.php +++ b/lang/sl/entities.php @@ -173,6 +173,7 @@ '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' => 'Razvrsti knjigo :bookName', 'books_sort_name' => 'Razvrsti po imenu', 'books_sort_created' => 'Razvrsti po datumu nastanka', diff --git a/lang/sl/settings.php b/lang/sl/settings.php index 947621389f4..87c1e8e6db2 100644 --- a/lang/sl/settings.php +++ b/lang/sl/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Vse', 'role_own' => 'Lasten', 'role_controlled_by_asset' => 'Nadzira ga sredstvo, v katerega so naloženi', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Shrani vlogo', 'role_users' => 'Uporabniki v tej vlogi', 'role_users_none' => 'Tej vlogi trenutno ni dodeljen noben uporabnik', diff --git a/lang/sq/entities.php b/lang/sq/entities.php index 74c50be3b2f..5501d2bc229 100644 --- a/lang/sq/entities.php +++ b/lang/sq/entities.php @@ -173,6 +173,7 @@ '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', diff --git a/lang/sq/settings.php b/lang/sq/settings.php index c4d1eb136eb..3937c650f86 100644 --- a/lang/sq/settings.php +++ b/lang/sq/settings.php @@ -207,6 +207,7 @@ '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', diff --git a/lang/sr/entities.php b/lang/sr/entities.php index 151edee40fa..8f9c40e91a3 100644 --- a/lang/sr/entities.php +++ b/lang/sr/entities.php @@ -173,6 +173,7 @@ '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', diff --git a/lang/sr/settings.php b/lang/sr/settings.php index f6c86827e69..3453bc344b3 100644 --- a/lang/sr/settings.php +++ b/lang/sr/settings.php @@ -207,6 +207,7 @@ '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', diff --git a/lang/sv/entities.php b/lang/sv/entities.php index 680e0908aa7..94fc23f501a 100644 --- a/lang/sv/entities.php +++ b/lang/sv/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Flytta kapitel och sidor inom en bok för att omorganisera dess innehåll. Andra böcker kan läggas till, vilket gör det enkelt att flytta kapitel och sidor mellan böcker. Du kan även ställa in en regel som automatiskt sorterar bokens innehåll vid ändringar.', 'books_sort_auto_sort' => 'Automatiskt sorteringsalternativ', 'books_sort_auto_sort_active' => 'Aktiv automatisk sorteringsregel: :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' => 'Sortera boken :bookName', 'books_sort_name' => 'Sortera utifrån namn', 'books_sort_created' => 'Sortera utifrån skapelse', diff --git a/lang/sv/settings.php b/lang/sv/settings.php index 773c4bff35e..47f602b8df8 100644 --- a/lang/sv/settings.php +++ b/lang/sv/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Alla', 'role_own' => 'Egna', 'role_controlled_by_asset' => 'Kontrolleras av den sida de laddas upp till', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Spara roll', 'role_users' => 'Användare med denna roll', 'role_users_none' => 'Inga användare tillhör den här rollen', diff --git a/lang/th/activities.php b/lang/th/activities.php new file mode 100644 index 00000000000..6d84813d6ac --- /dev/null +++ b/lang/th/activities.php @@ -0,0 +1,140 @@ + 'สร้างหน้า', + 'page_create_notification' => 'สร้างหน้าสำเร็จแล้ว', + 'page_update' => 'แก้ไขหน้า', + 'page_update_notification' => 'แก้ไขหน้าสำเร็จแล้ว', + 'page_delete' => 'ลบหน้า', + 'page_delete_notification' => 'ลบหน้าสำเร็จแล้ว', + 'page_restore' => 'กู้คืนหน้า', + 'page_restore_notification' => 'กู้คืนหน้าสำเร็จแล้ว', + 'page_move' => 'ย้ายหน้า', + 'page_move_notification' => 'ย้ายหน้าสำเร็จแล้ว', + + // Chapters + 'chapter_create' => 'สร้างบท', + 'chapter_create_notification' => 'สร้างบทสำเร็จแล้ว', + 'chapter_update' => 'แก้ไขบท', + 'chapter_update_notification' => 'แก้ไขบทสำเร็จแล้ว', + 'chapter_delete' => 'ลบบท', + 'chapter_delete_notification' => 'ลบบทสำเร็จแล้ว', + 'chapter_move' => 'ย้ายบท', + 'chapter_move_notification' => 'ย้ายบทสำเร็จแล้ว', + + // Books + 'book_create' => 'สร้างหนังสือ', + 'book_create_notification' => 'สร้างหนังสือสำเร็จแล้ว', + 'book_create_from_chapter' => 'แปลงบทเป็นหนังสือ', + 'book_create_from_chapter_notification' => 'แปลงบทเป็นหนังสือสำเร็จแล้ว', + 'book_update' => 'แก้ไขหนังสือ', + 'book_update_notification' => 'แก้ไขหนังสือสำเร็จแล้ว', + 'book_delete' => 'ลบหนังสือ', + 'book_delete_notification' => 'ลบหนังสือสำเร็จแล้ว', + 'book_sort' => 'จัดเรียงหนังสือ', + 'book_sort_notification' => 'จัดเรียงหนังสือสำเร็จแล้ว', + + // Bookshelves + 'bookshelf_create' => 'สร้างชั้นวาง', + 'bookshelf_create_notification' => 'สร้างชั้นวางสำเร็จแล้ว', + 'bookshelf_create_from_book' => 'แปลงหนังสือเป็นชั้นวาง', + 'bookshelf_create_from_book_notification' => 'แปลงหนังสือเป็นชั้นวางสำเร็จแล้ว', + 'bookshelf_update' => 'แก้ไขชั้นวาง', + 'bookshelf_update_notification' => 'แก้ไขชั้นวางสำเร็จแล้ว', + 'bookshelf_delete' => 'ลบชั้นวาง', + 'bookshelf_delete_notification' => 'ลบชั้นวางสำเร็จแล้ว', + + // Revisions + 'revision_restore' => 'กู้คืนการแก้ไข', + 'revision_delete' => 'ลบการแก้ไข', + 'revision_delete_notification' => 'ลบการแก้ไขสำเร็จแล้ว', + + // Favourites + 'favourite_add_notification' => 'เพิ่ม ":name" ในรายการโปรดแล้ว', + 'favourite_remove_notification' => 'นำ ":name" ออกจากรายการโปรดแล้ว', + + // Watching + 'watch_update_level_notification' => 'อัปเดตการตั้งค่าการติดตามสำเร็จแล้ว', + + // Auth + 'auth_login' => 'เข้าสู่ระบบ', + 'auth_register' => 'ลงทะเบียนเป็นผู้ใช้ใหม่', + 'auth_password_reset_request' => 'ขอรีเซ็ตรหัสผ่าน', + 'auth_password_reset_update' => 'รีเซ็ตรหัสผ่านแล้ว', + 'mfa_setup_method' => 'ตั้งค่าวิธียืนยันตัวตน MFA', + 'mfa_setup_method_notification' => 'ตั้งค่าการยืนยันตัวตนแบบหลายขั้นตอนสำเร็จแล้ว', + 'mfa_remove_method' => 'ลบวิธียืนยันตัวตน MFA', + 'mfa_remove_method_notification' => 'ลบการยืนยันตัวตนแบบหลายขั้นตอนสำเร็จแล้ว', + + // Settings + 'settings_update' => 'แก้ไขการตั้งค่า', + 'settings_update_notification' => 'แก้ไขการตั้งค่าสำเร็จแล้ว', + 'maintenance_action_run' => 'ดำเนินการบำรุงรักษาระบบ', + + // Webhooks + 'webhook_create' => 'สร้าง Webhook', + 'webhook_create_notification' => 'สร้าง Webhook สำเร็จแล้ว', + 'webhook_update' => 'แก้ไข Webhook', + 'webhook_update_notification' => 'แก้ไข Webhook สำเร็จแล้ว', + 'webhook_delete' => 'ลบ Webhook', + 'webhook_delete_notification' => 'ลบ Webhook สำเร็จแล้ว', + + // Imports + 'import_create' => 'สร้างการนำเข้า', + 'import_create_notification' => 'อัปโหลดไฟล์นำเข้าสำเร็จแล้ว', + 'import_run' => 'ดำเนินการนำเข้า', + 'import_run_notification' => 'นำเข้าเนื้อหาสำเร็จแล้ว', + 'import_delete' => 'ลบการนำเข้า', + 'import_delete_notification' => 'ลบการนำเข้าสำเร็จแล้ว', + + // Users + 'user_create' => 'สร้างผู้ใช้', + 'user_create_notification' => 'สร้างผู้ใช้สำเร็จแล้ว', + 'user_update' => 'แก้ไขผู้ใช้', + 'user_update_notification' => 'แก้ไขผู้ใช้สำเร็จแล้ว', + 'user_delete' => 'ลบผู้ใช้', + 'user_delete_notification' => 'ลบผู้ใช้สำเร็จแล้ว', + + // API Tokens + 'api_token_create' => 'สร้าง API Token', + 'api_token_create_notification' => 'สร้าง API Token สำเร็จแล้ว', + 'api_token_update' => 'แก้ไข API Token', + 'api_token_update_notification' => 'แก้ไข API Token สำเร็จแล้ว', + 'api_token_delete' => 'ลบ API Token', + 'api_token_delete_notification' => 'ลบ API Token สำเร็จแล้ว', + + // Roles + 'role_create' => 'สร้างบทบาท', + 'role_create_notification' => 'สร้างบทบาทสำเร็จแล้ว', + 'role_update' => 'แก้ไขบทบาท', + 'role_update_notification' => 'แก้ไขบทบาทสำเร็จแล้ว', + 'role_delete' => 'ลบบทบาท', + 'role_delete_notification' => 'ลบบทบาทสำเร็จแล้ว', + + // Recycle Bin + 'recycle_bin_empty' => 'ล้างถังรีไซเคิล', + 'recycle_bin_restore' => 'กู้คืนจากถังรีไซเคิล', + 'recycle_bin_destroy' => 'ลบถาวรจากถังรีไซเคิล', + + // Comments + 'commented_on' => 'แสดงความคิดเห็นใน', + 'comment_create' => 'เพิ่มความคิดเห็น', + 'comment_update' => 'แก้ไขความคิดเห็น', + 'comment_delete' => 'ลบความคิดเห็น', + + // Sort Rules + 'sort_rule_create' => 'สร้างกฎการจัดเรียง', + 'sort_rule_create_notification' => 'สร้างกฎการจัดเรียงสำเร็จแล้ว', + 'sort_rule_update' => 'แก้ไขกฎการจัดเรียง', + 'sort_rule_update_notification' => 'แก้ไขกฎการจัดเรียงสำเร็จแล้ว', + 'sort_rule_delete' => 'ลบกฎการจัดเรียง', + 'sort_rule_delete_notification' => 'ลบกฎการจัดเรียงสำเร็จแล้ว', + + // Other + 'permissions_update' => 'แก้ไขสิทธิ์', +]; diff --git a/lang/th/auth.php b/lang/th/auth.php new file mode 100644 index 00000000000..c00b29bcd88 --- /dev/null +++ b/lang/th/auth.php @@ -0,0 +1,117 @@ + 'ข้อมูลประจำตัวไม่ตรงกับที่มีในระบบ', + 'throttle' => 'เข้าสู่ระบบล้มเหลวหลายครั้งเกินไป กรุณาลองใหม่ในอีก :seconds วินาที', + + // Login & Register + 'sign_up' => 'สมัครสมาชิก', + 'log_in' => 'เข้าสู่ระบบ', + 'log_in_with' => 'เข้าสู่ระบบด้วย :socialDriver', + 'sign_up_with' => 'สมัครสมาชิกด้วย :socialDriver', + 'logout' => 'ออกจากระบบ', + + 'name' => 'ชื่อ', + 'username' => 'ชื่อผู้ใช้', + 'email' => 'อีเมล', + 'password' => 'รหัสผ่าน', + 'password_confirm' => 'ยืนยันรหัสผ่าน', + 'password_hint' => 'ต้องมีอย่างน้อย 8 ตัวอักษร', + 'forgot_password' => 'ลืมรหัสผ่าน?', + 'remember_me' => 'จดจำฉัน', + 'ldap_email_hint' => 'กรุณากรอกอีเมลที่จะใช้กับบัญชีนี้', + 'create_account' => 'สร้างบัญชี', + 'already_have_account' => 'มีบัญชีอยู่แล้ว?', + 'dont_have_account' => 'ยังไม่มีบัญชี?', + 'social_login' => 'เข้าสู่ระบบด้วย Social', + 'social_registration' => 'ลงทะเบียนด้วย Social', + 'social_registration_text' => 'ลงทะเบียนและเข้าสู่ระบบด้วยบริการอื่น', + + 'register_thanks' => 'ขอบคุณที่ลงทะเบียน กรุณายืนยันอีเมลเพื่อเข้าสู่ระบบ', + 'register_confirm' => 'ยืนยันและลงทะเบียน', + 'registrations_disabled' => 'ขณะนี้ปิดรับการลงทะเบียน', + 'registration_email_domain_invalid' => 'โดเมนอีเมลนี้ไม่มีสิทธิ์เข้าถึงระบบ', + 'register_success' => 'ขอบคุณที่สมัครสมาชิก คุณได้ลงทะเบียนและเข้าสู่ระบบแล้ว', + + // Login auto-initiation + 'auto_init_starting' => 'กำลังเข้าสู่ระบบ', + 'auto_init_starting_desc' => 'กำลังติดต่อระบบยืนยันตัวตนเพื่อเริ่มกระบวนการเข้าสู่ระบบ หากไม่มีความคืบหน้าภายใน 5 วินาที กรุณาคลิกลิงก์ด้านล่าง', + 'auto_init_start_link' => 'ดำเนินการยืนยันตัวตน', + + // Password Reset + 'reset_password' => 'รีเซ็ตรหัสผ่าน', + 'reset_password_send_instructions' => 'กรอกอีเมลด้านล่าง ระบบจะส่งลิงก์รีเซ็ตรหัสผ่านให้คุณ', + 'reset_password_send_button' => 'ส่งลิงก์รีเซ็ตรหัสผ่าน', + 'reset_password_sent' => 'หากพบอีเมล :email ในระบบ จะมีลิงก์รีเซ็ตรหัสผ่านส่งไปให้', + 'reset_password_success' => 'รีเซ็ตรหัสผ่านสำเร็จแล้ว', + 'email_reset_subject' => 'รีเซ็ตรหัสผ่าน :appName ของคุณ', + 'email_reset_text' => 'คุณได้รับอีเมลนี้เพราะมีการขอรีเซ็ตรหัสผ่านสำหรับบัญชีของคุณ', + 'email_reset_not_requested' => 'หากคุณไม่ได้ขอรีเซ็ตรหัสผ่าน ไม่ต้องดำเนินการใดๆ เพิ่มเติม', + + // Email Confirmation + 'email_confirm_subject' => 'ยืนยันอีเมลของคุณบน :appName', + 'email_confirm_greeting' => 'ขอบคุณที่เข้าร่วม :appName!', + 'email_confirm_text' => 'กรุณายืนยันอีเมลของคุณโดยคลิกปุ่มด้านล่าง:', + 'email_confirm_action' => 'ยืนยันอีเมล', + 'email_confirm_send_error' => 'จำเป็นต้องยืนยันอีเมล แต่ระบบไม่สามารถส่งอีเมลได้ กรุณาติดต่อผู้ดูแลระบบเพื่อตรวจสอบการตั้งค่าอีเมล', + 'email_confirm_success' => 'ยืนยันอีเมลสำเร็จแล้ว สามารถเข้าสู่ระบบได้', + 'email_confirm_resent' => 'ส่งอีเมลยืนยันใหม่แล้ว กรุณาตรวจสอบกล่องจดหมาย', + 'email_confirm_thanks' => 'ขอบคุณที่ยืนยัน!', + 'email_confirm_thanks_desc' => 'กรุณารอสักครู่ขณะที่ระบบดำเนินการยืนยัน หากไม่ถูกเปลี่ยนหน้าภายใน 3 วินาที กรุณาคลิกลิงก์ "ดำเนินการต่อ" ด้านล่าง', + + 'email_not_confirmed' => 'ยังไม่ได้ยืนยันอีเมล', + 'email_not_confirmed_text' => 'อีเมลของคุณยังไม่ได้รับการยืนยัน', + 'email_not_confirmed_click_link' => 'กรุณาคลิกลิงก์ในอีเมลที่ส่งให้คุณหลังจากลงทะเบียน', + 'email_not_confirmed_resend' => 'หากไม่พบอีเมล คุณสามารถส่งอีเมลยืนยันอีกครั้งโดยกรอกแบบฟอร์มด้านล่าง', + 'email_not_confirmed_resend_button' => 'ส่งอีเมลยืนยันอีกครั้ง', + + // User Invite + 'user_invite_email_subject' => 'คุณได้รับเชิญให้เข้าร่วม :appName!', + 'user_invite_email_greeting' => 'มีการสร้างบัญชีให้คุณบน :appName', + 'user_invite_email_text' => 'คลิกปุ่มด้านล่างเพื่อตั้งรหัสผ่านและเข้าใช้งาน:', + 'user_invite_email_action' => 'ตั้งรหัสผ่านบัญชี', + 'user_invite_page_welcome' => 'ยินดีต้อนรับสู่ :appName!', + 'user_invite_page_text' => 'เพื่อเสร็จสิ้นการสร้างบัญชีและเข้าใช้งาน คุณต้องตั้งรหัสผ่านสำหรับเข้าสู่ :appName ในครั้งถัดไป', + 'user_invite_page_confirm_button' => 'ยืนยันรหัสผ่าน', + 'user_invite_success_login' => 'ตั้งรหัสผ่านแล้ว คุณสามารถเข้าสู่ระบบ :appName ด้วยรหัสผ่านที่ตั้งไว้ได้แล้ว!', + + // Multi-factor Authentication + 'mfa_setup' => 'ตั้งค่าการยืนยันตัวตนแบบหลายขั้นตอน', + 'mfa_setup_desc' => 'ตั้งค่า MFA เพื่อเพิ่มความปลอดภัยให้บัญชีของคุณ', + 'mfa_setup_configured' => 'ตั้งค่าแล้ว', + 'mfa_setup_reconfigure' => 'ตั้งค่าใหม่', + 'mfa_setup_remove_confirmation' => 'คุณแน่ใจหรือไม่ว่าต้องการลบวิธียืนยันตัวตนแบบหลายขั้นตอนนี้?', + 'mfa_setup_action' => 'ตั้งค่า', + 'mfa_backup_codes_usage_limit_warning' => 'คุณมีรหัสสำรองเหลือน้อยกว่า 5 รหัส กรุณาสร้างและบันทึกชุดใหม่ก่อนหมด เพื่อป้องกันการถูกล็อกออกจากบัญชี', + 'mfa_option_totp_title' => 'แอป Authenticator', + 'mfa_option_totp_desc' => 'ในการใช้การยืนยันตัวตนแบบหลายขั้นตอน คุณต้องมีแอปพลิเคชันมือถือที่รองรับ TOTP เช่น Google Authenticator, Authy หรือ Microsoft Authenticator', + 'mfa_option_backup_codes_title' => 'รหัสสำรอง', + 'mfa_option_backup_codes_desc' => 'สร้างชุดรหัสสำรองแบบใช้ครั้งเดียว ซึ่งจะใช้กรอกเมื่อเข้าสู่ระบบเพื่อยืนยันตัวตน กรุณาเก็บรักษาไว้ในที่ปลอดภัย', + 'mfa_gen_confirm_and_enable' => 'ยืนยันและเปิดใช้งาน', + 'mfa_gen_backup_codes_title' => 'ตั้งค่ารหัสสำรอง', + 'mfa_gen_backup_codes_desc' => 'บันทึกรายการรหัสด้านล่างไว้ในที่ปลอดภัย เมื่อเข้าระบบคุณสามารถใช้รหัสเหล่านี้เป็นการยืนยันตัวตนขั้นที่สองได้', + 'mfa_gen_backup_codes_download' => 'ดาวน์โหลดรหัส', + 'mfa_gen_backup_codes_usage_warning' => 'รหัสแต่ละรหัสใช้ได้เพียงครั้งเดียว', + 'mfa_gen_totp_title' => 'ตั้งค่าแอปมือถือ', + 'mfa_gen_totp_desc' => 'ในการใช้การยืนยันตัวตนแบบหลายขั้นตอน คุณต้องมีแอปพลิเคชันมือถือที่รองรับ TOTP เช่น Google Authenticator, Authy หรือ Microsoft Authenticator', + 'mfa_gen_totp_scan' => 'สแกน QR code ด้านล่างด้วยแอป Authenticator ที่คุณต้องการใช้', + 'mfa_gen_totp_verify_setup' => 'ยืนยันการตั้งค่า', + 'mfa_gen_totp_verify_setup_desc' => 'ยืนยันว่าทุกอย่างทำงานได้โดยกรอกรหัสที่สร้างจากแอป Authenticator ในช่องด้านล่าง:', + 'mfa_gen_totp_provide_code_here' => 'กรอกรหัสที่สร้างจากแอปของคุณที่นี่', + 'mfa_verify_access' => 'ยืนยันการเข้าถึง', + 'mfa_verify_access_desc' => 'บัญชีของคุณต้องยืนยันตัวตนผ่านการตรวจสอบเพิ่มเติมก่อนเข้าใช้งาน กรุณายืนยันด้วยวิธีที่ตั้งค่าไว้เพื่อดำเนินการต่อ', + 'mfa_verify_no_methods' => 'ยังไม่ได้ตั้งค่าวิธียืนยันตัวตน', + 'mfa_verify_no_methods_desc' => 'ไม่พบวิธียืนยันตัวตนแบบหลายขั้นตอนสำหรับบัญชีของคุณ กรุณาตั้งค่าอย่างน้อยหนึ่งวิธีก่อนเข้าใช้งาน', + 'mfa_verify_use_totp' => 'ยืนยันด้วยแอปมือถือ', + 'mfa_verify_use_backup_codes' => 'ยืนยันด้วยรหัสสำรอง', + 'mfa_verify_backup_code' => 'รหัสสำรอง', + 'mfa_verify_backup_code_desc' => 'กรอกรหัสสำรองที่เหลืออยู่ของคุณด้านล่าง:', + 'mfa_verify_backup_code_enter_here' => 'กรอกรหัสสำรองที่นี่', + 'mfa_verify_totp_desc' => 'กรอกรหัสที่สร้างจากแอปมือถือของคุณด้านล่าง:', + 'mfa_setup_login_notification' => 'ตั้งค่าวิธียืนยันตัวตนแล้ว กรุณาเข้าสู่ระบบอีกครั้งด้วยวิธีที่ตั้งค่าไว้', +]; diff --git a/lang/th/common.php b/lang/th/common.php new file mode 100644 index 00000000000..9a8b4fcc5df --- /dev/null +++ b/lang/th/common.php @@ -0,0 +1,115 @@ + 'ยกเลิก', + 'close' => 'ปิด', + 'confirm' => 'ยืนยัน', + 'back' => 'ย้อนกลับ', + 'save' => 'บันทึก', + 'continue' => 'ดำเนินการต่อ', + 'select' => 'เลือก', + 'toggle_all' => 'สลับทั้งหมด', + 'more' => 'เพิ่มเติม', + + // Form Labels + 'name' => 'ชื่อ', + 'description' => 'คำอธิบาย', + 'role' => 'บทบาท', + 'cover_image' => 'ภาพปก', + 'cover_image_description' => 'รูปภาพนี้ควรมีขนาดประมาณ 440x250px แต่จะถูกปรับขนาดและตัดให้เหมาะกับการแสดงผลในสถานการณ์ต่างๆ ดังนั้นขนาดที่แสดงจริงอาจแตกต่างกัน', + + // Actions + 'actions' => 'การดำเนินการ', + 'view' => 'ดู', + 'view_all' => 'ดูทั้งหมด', + 'new' => 'ใหม่', + 'create' => 'สร้าง', + 'update' => 'อัปเดต', + 'edit' => 'แก้ไข', + 'archive' => 'เก็บถาวร', + 'unarchive' => 'ยกเลิกการเก็บถาวร', + 'sort' => 'จัดเรียง', + 'move' => 'ย้าย', + 'copy' => 'คัดลอก', + 'reply' => 'ตอบกลับ', + 'delete' => 'ลบ', + 'delete_confirm' => 'ยืนยันการลบ', + 'search' => 'ค้นหา', + 'search_clear' => 'ล้างการค้นหา', + 'reset' => 'รีเซ็ต', + 'remove' => 'ลบออก', + 'add' => 'เพิ่ม', + 'configure' => 'กำหนดค่า', + 'manage' => 'จัดการ', + 'fullscreen' => 'เต็มหน้าจอ', + 'favourite' => 'เพิ่มในรายการโปรด', + 'unfavourite' => 'นำออกจากรายการโปรด', + 'next' => 'ถัดไป', + 'previous' => 'ก่อนหน้า', + 'filter_active' => 'ตัวกรองที่ใช้งานอยู่:', + 'filter_clear' => 'ล้างตัวกรอง', + 'download' => 'ดาวน์โหลด', + 'open_in_tab' => 'เปิดในแท็บใหม่', + 'open' => 'เปิด', + + // Sort Options + 'sort_options' => 'ตัวเลือกการจัดเรียง', + 'sort_direction_toggle' => 'สลับทิศทางการจัดเรียง', + 'sort_ascending' => 'จัดเรียงจากน้อยไปมาก', + 'sort_descending' => 'จัดเรียงจากมากไปน้อย', + 'sort_name' => 'ชื่อ', + 'sort_default' => 'ค่าเริ่มต้น', + 'sort_created_at' => 'วันที่สร้าง', + 'sort_updated_at' => 'วันที่แก้ไขล่าสุด', + + // Misc + 'deleted_user' => 'ผู้ใช้ที่ถูกลบ', + 'no_activity' => 'ไม่มีกิจกรรมที่จะแสดง', + 'no_items' => 'ไม่มีรายการ', + 'back_to_top' => 'กลับไปด้านบน', + 'skip_to_main_content' => 'ข้ามไปยังเนื้อหาหลัก', + 'toggle_details' => 'แสดง/ซ่อนรายละเอียด', + 'toggle_thumbnails' => 'แสดง/ซ่อนภาพย่อ', + 'details' => 'รายละเอียด', + 'grid_view' => 'มุมมองตาราง', + 'list_view' => 'มุมมองรายการ', + 'default' => 'ค่าเริ่มต้น', + 'breadcrumb' => 'เส้นทางนำทาง', + 'status' => 'สถานะ', + 'status_active' => 'ใช้งาน', + 'status_inactive' => 'ไม่ใช้งาน', + 'never' => 'ไม่เคย', + 'none' => 'ไม่มี', + + // Header + 'homepage' => 'หน้าแรก', + 'header_menu_expand' => 'ขยายเมนูส่วนหัว', + 'profile_menu' => 'เมนูโปรไฟล์', + 'view_profile' => 'ดูโปรไฟล์', + 'edit_profile' => 'แก้ไขโปรไฟล์', + 'dark_mode' => 'โหมดมืด', + 'light_mode' => 'โหมดสว่าง', + 'global_search' => 'ค้นหาทั้งระบบ', + + // Layout tabs + 'tab_info' => 'ข้อมูล', + 'tab_info_label' => 'แท็บ: แสดงข้อมูลเพิ่มเติม', + 'tab_content' => 'เนื้อหา', + 'tab_content_label' => 'แท็บ: แสดงเนื้อหาหลัก', + + // Email Content + 'email_action_help' => 'หากไม่สามารถคลิกปุ่ม ":actionText" ได้ กรุณาคัดลอก URL ด้านล่างและวางในเบราว์เซอร์:', + 'email_rights' => 'สงวนลิขสิทธิ์', + + // Footer Link Options + // Not directly used but available for convenience to users. + 'privacy_policy' => 'นโยบายความเป็นส่วนตัว', + 'terms_of_service' => 'ข้อกำหนดการใช้งาน', + + // OpenSearch + 'opensearch_description' => 'ค้นหา :appName', +]; diff --git a/lang/th/components.php b/lang/th/components.php new file mode 100644 index 00000000000..2931d2bc768 --- /dev/null +++ b/lang/th/components.php @@ -0,0 +1,46 @@ + 'เลือกรูปภาพ', + 'image_list' => 'รายการรูปภาพ', + 'image_details' => 'รายละเอียดรูปภาพ', + 'image_upload' => 'อัปโหลดรูปภาพ', + 'image_intro' => 'คุณสามารถเลือกและจัดการรูปภาพที่เคยอัปโหลดไว้ในระบบได้ที่นี่', + 'image_intro_upload' => 'อัปโหลดรูปภาพใหม่โดยลากไฟล์รูปภาพมาวางในหน้าต่างนี้ หรือใช้ปุ่ม "อัปโหลดรูปภาพ" ด้านบน', + 'image_all' => 'ทั้งหมด', + 'image_all_title' => 'ดูรูปภาพทั้งหมด', + 'image_book_title' => 'ดูรูปภาพที่อัปโหลดในหนังสือนี้', + 'image_page_title' => 'ดูรูปภาพที่อัปโหลดในหน้านี้', + 'image_search_hint' => 'ค้นหาตามชื่อรูปภาพ', + 'image_uploaded' => 'อัปโหลดเมื่อ :uploadedDate', + 'image_uploaded_by' => 'อัปโหลดโดย :userName', + 'image_uploaded_to' => 'อัปโหลดไปยัง :pageLink', + 'image_updated' => 'อัปเดตเมื่อ :updateDate', + 'image_load_more' => 'โหลดเพิ่มเติม', + 'image_image_name' => 'ชื่อรูปภาพ', + 'image_delete_used' => 'รูปภาพนี้ถูกใช้งานในหน้าด้านล่าง', + 'image_delete_confirm_text' => 'คุณแน่ใจหรือไม่ว่าต้องการลบรูปภาพนี้?', + 'image_select_image' => 'เลือกรูปภาพ', + 'image_dropzone' => 'วางรูปภาพหรือคลิกที่นี่เพื่ออัปโหลด', + 'image_dropzone_drop' => 'วางรูปภาพที่นี่เพื่ออัปโหลด', + 'images_deleted' => 'ลบรูปภาพแล้ว', + 'image_preview' => 'ดูตัวอย่างรูปภาพ', + 'image_upload_success' => 'อัปโหลดรูปภาพสำเร็จแล้ว', + 'image_update_success' => 'อัปเดตรายละเอียดรูปภาพสำเร็จแล้ว', + 'image_delete_success' => 'ลบรูปภาพสำเร็จแล้ว', + 'image_replace' => 'แทนที่รูปภาพ', + 'image_replace_success' => 'อัปเดตไฟล์รูปภาพสำเร็จแล้ว', + 'image_rebuild_thumbs' => 'สร้างภาพย่อขนาดต่างๆ ใหม่', + 'image_rebuild_thumbs_success' => 'สร้างภาพย่อขนาดต่างๆ ใหม่สำเร็จแล้ว!', + + // Code Editor + 'code_editor' => 'แก้ไขโค้ด', + 'code_language' => 'ภาษาโค้ด', + 'code_content' => 'เนื้อหาโค้ด', + 'code_session_history' => 'ประวัติเซสชัน', + 'code_save' => 'บันทึกโค้ด', +]; diff --git a/lang/th/editor.php b/lang/th/editor.php new file mode 100644 index 00000000000..4af059bf19c --- /dev/null +++ b/lang/th/editor.php @@ -0,0 +1,182 @@ + 'ทั่วไป', + 'advanced' => 'ขั้นสูง', + 'none' => 'ไม่มี', + 'cancel' => 'ยกเลิก', + 'save' => 'บันทึก', + 'close' => 'ปิด', + 'apply' => 'ใช้งาน', + 'undo' => 'เลิกทำ', + 'redo' => 'ทำซ้ำ', + 'left' => 'ซ้าย', + 'center' => 'กลาง', + 'right' => 'ขวา', + 'top' => 'บน', + 'middle' => 'กลาง', + 'bottom' => 'ล่าง', + 'width' => 'ความกว้าง', + 'height' => 'ความสูง', + 'More' => 'เพิ่มเติม', + 'select' => 'เลือก...', + + // Toolbar + 'formats' => 'รูปแบบ', + 'header_large' => 'หัวข้อใหญ่', + 'header_medium' => 'หัวข้อกลาง', + 'header_small' => 'หัวข้อเล็ก', + 'header_tiny' => 'หัวข้อเล็กมาก', + 'paragraph' => 'ย่อหน้า', + 'blockquote' => 'คำพูดอ้างอิง', + 'inline_code' => 'โค้ดแบบอินไลน์', + 'callouts' => 'กล่องข้อความเน้น', + 'callout_information' => 'ข้อมูล', + 'callout_success' => 'สำเร็จ', + 'callout_warning' => 'คำเตือน', + 'callout_danger' => 'อันตราย', + 'bold' => 'ตัวหนา', + 'italic' => 'ตัวเอียง', + 'underline' => 'ขีดเส้นใต้', + 'strikethrough' => 'ขีดทับ', + 'superscript' => 'ตัวยก', + 'subscript' => 'ตัวห้อย', + 'text_color' => 'สีตัวอักษร', + 'highlight_color' => 'สีไฮไลต์', + 'custom_color' => 'สีกำหนดเอง', + 'remove_color' => 'ลบสี', + 'background_color' => 'สีพื้นหลัง', + 'align_left' => 'จัดชิดซ้าย', + 'align_center' => 'จัดกึ่งกลาง', + 'align_right' => 'จัดชิดขวา', + 'align_justify' => 'จัดเต็มบรรทัด', + 'list_bullet' => 'รายการแบบจุด', + 'list_numbered' => 'รายการแบบตัวเลข', + 'list_task' => 'รายการงาน', + 'indent_increase' => 'เพิ่มการย่อหน้า', + 'indent_decrease' => 'ลดการย่อหน้า', + 'table' => 'ตาราง', + 'insert_image' => 'แทรกรูปภาพ', + 'insert_image_title' => 'แทรก/แก้ไขรูปภาพ', + 'insert_link' => 'แทรก/แก้ไขลิงก์', + 'insert_link_title' => 'แทรก/แก้ไขลิงก์', + 'insert_horizontal_line' => 'แทรกเส้นแนวนอน', + 'insert_code_block' => 'แทรกบล็อกโค้ด', + 'edit_code_block' => 'แก้ไขบล็อกโค้ด', + 'insert_drawing' => 'แทรก/แก้ไขภาพวาด', + 'drawing_manager' => 'ตัวจัดการภาพวาด', + 'insert_media' => 'แทรก/แก้ไขสื่อ', + 'insert_media_title' => 'แทรก/แก้ไขสื่อ', + 'clear_formatting' => 'ล้างการจัดรูปแบบ', + 'source_code' => 'ซอร์สโค้ด', + 'source_code_title' => 'ซอร์สโค้ด', + 'fullscreen' => 'เต็มหน้าจอ', + 'image_options' => 'ตัวเลือกรูปภาพ', + + // Tables + 'table_properties' => 'คุณสมบัติตาราง', + 'table_properties_title' => 'คุณสมบัติตาราง', + 'delete_table' => 'ลบตาราง', + 'table_clear_formatting' => 'ล้างการจัดรูปแบบตาราง', + 'resize_to_contents' => 'ปรับขนาดตามเนื้อหา', + 'row_header' => 'แถวหัวตาราง', + 'insert_row_before' => 'แทรกแถวก่อนหน้า', + 'insert_row_after' => 'แทรกแถวถัดไป', + 'delete_row' => 'ลบแถว', + 'insert_column_before' => 'แทรกคอลัมน์ก่อนหน้า', + 'insert_column_after' => 'แทรกคอลัมน์ถัดไป', + 'delete_column' => 'ลบคอลัมน์', + 'table_cell' => 'เซลล์', + 'table_row' => 'แถว', + 'table_column' => 'คอลัมน์', + 'cell_properties' => 'คุณสมบัติเซลล์', + 'cell_properties_title' => 'คุณสมบัติเซลล์', + 'cell_type' => 'ประเภทเซลล์', + 'cell_type_cell' => 'เซลล์', + 'cell_scope' => 'ขอบเขต', + 'cell_type_header' => 'เซลล์หัวตาราง', + 'merge_cells' => 'รวมเซลล์', + 'split_cell' => 'แยกเซลล์', + 'table_row_group' => 'กลุ่มแถว', + 'table_column_group' => 'กลุ่มคอลัมน์', + 'horizontal_align' => 'การจัดแนวนอน', + 'vertical_align' => 'การจัดแนวตั้ง', + 'border_width' => 'ความกว้างเส้นขอบ', + 'border_style' => 'รูปแบบเส้นขอบ', + 'border_color' => 'สีเส้นขอบ', + 'row_properties' => 'คุณสมบัติแถว', + 'row_properties_title' => 'คุณสมบัติแถว', + 'cut_row' => 'ตัดแถว', + 'copy_row' => 'คัดลอกแถว', + 'paste_row_before' => 'วางแถวก่อนหน้า', + 'paste_row_after' => 'วางแถวถัดไป', + 'row_type' => 'ประเภทแถว', + 'row_type_header' => 'ส่วนหัว', + 'row_type_body' => 'ส่วนเนื้อหา', + 'row_type_footer' => 'ส่วนท้าย', + 'alignment' => 'การจัดตำแหน่ง', + 'cut_column' => 'ตัดคอลัมน์', + 'copy_column' => 'คัดลอกคอลัมน์', + 'paste_column_before' => 'วางคอลัมน์ก่อนหน้า', + 'paste_column_after' => 'วางคอลัมน์ถัดไป', + 'cell_padding' => 'ระยะห่างภายในเซลล์', + 'cell_spacing' => 'ระยะห่างระหว่างเซลล์', + 'caption' => 'คำบรรยาย', + 'show_caption' => 'แสดงคำบรรยาย', + 'constrain' => 'รักษาสัดส่วน', + 'cell_border_solid' => 'เส้นทึบ', + 'cell_border_dotted' => 'เส้นจุด', + 'cell_border_dashed' => 'เส้นประ', + 'cell_border_double' => 'เส้นคู่', + 'cell_border_groove' => 'เส้นร่อง', + 'cell_border_ridge' => 'เส้นนูน', + 'cell_border_inset' => 'เส้นฝัง', + 'cell_border_outset' => 'เส้นนูนออก', + 'cell_border_none' => 'ไม่มี', + 'cell_border_hidden' => 'ซ่อน', + + // Images, links, details/summary & embed + 'source' => 'แหล่งที่มา', + 'alt_desc' => 'คำอธิบายทดแทน', + 'embed' => 'ฝังเนื้อหา', + 'paste_embed' => 'วางโค้ดฝังเนื้อหาด้านล่าง:', + 'url' => 'URL', + 'text_to_display' => 'ข้อความที่แสดง', + 'title' => 'ชื่อเรื่อง', + 'browse_links' => 'เรียกดูลิงก์', + 'open_link' => 'เปิดลิงก์', + 'open_link_in' => 'เปิดลิงก์ใน...', + 'open_link_current' => 'หน้าต่างปัจจุบัน', + 'open_link_new' => 'หน้าต่างใหม่', + 'remove_link' => 'ลบลิงก์', + 'insert_collapsible' => 'แทรกบล็อกที่ย่อได้', + 'collapsible_unwrap' => 'ยกเลิกการห่อ', + 'edit_label' => 'แก้ไขป้ายกำกับ', + 'toggle_open_closed' => 'สลับเปิด/ปิด', + 'collapsible_edit' => 'แก้ไขบล็อกที่ย่อได้', + 'toggle_label' => 'ป้ายกำกับสลับ', + + // About view + 'about' => 'เกี่ยวกับตัวแก้ไข', + 'about_title' => 'เกี่ยวกับตัวแก้ไข WYSIWYG', + 'editor_license' => 'สัญญาอนุญาตและลิขสิทธิ์ตัวแก้ไข', + 'editor_lexical_license' => 'ตัวแก้ไขนี้สร้างจาก :lexicalLink ซึ่งเผยแพร่ภายใต้สัญญาอนุญาต MIT', + 'editor_lexical_license_link' => 'ดูรายละเอียดสัญญาอนุญาตฉบับเต็มได้ที่นี่', + 'editor_tiny_license' => 'ตัวแก้ไขนี้สร้างด้วย :tinyLink ซึ่งเผยแพร่ภายใต้สัญญาอนุญาต MIT', + 'editor_tiny_license_link' => 'ดูรายละเอียดลิขสิทธิ์และสัญญาอนุญาตของ TinyMCE ได้ที่นี่', + 'save_continue' => 'บันทึกหน้าและดำเนินการต่อ', + 'callouts_cycle' => '(กดต่อเนื่องเพื่อสลับประเภท)', + 'link_selector' => 'ลิงก์ไปยังเนื้อหา', + 'shortcuts' => 'แป้นพิมพ์ลัด', + 'shortcut' => 'แป้นพิมพ์ลัด', + 'shortcuts_intro' => 'แป้นพิมพ์ลัดต่อไปนี้ใช้งานได้ในตัวแก้ไข:', + 'windows_linux' => '(Windows/Linux)', + 'mac' => '(Mac)', + 'description' => 'คำอธิบาย', +]; diff --git a/lang/th/entities.php b/lang/th/entities.php new file mode 100644 index 00000000000..1f0f30ec6ff --- /dev/null +++ b/lang/th/entities.php @@ -0,0 +1,477 @@ + 'สร้างล่าสุด', + 'recently_created_pages' => 'หน้าที่สร้างล่าสุด', + 'recently_updated_pages' => 'หน้าที่แก้ไขล่าสุด', + 'recently_created_chapters' => 'บทที่สร้างล่าสุด', + 'recently_created_books' => 'หนังสือที่สร้างล่าสุด', + 'recently_created_shelves' => 'ชั้นวางที่สร้างล่าสุด', + 'recently_update' => 'แก้ไขล่าสุด', + 'recently_viewed' => 'ดูล่าสุด', + 'recent_activity' => 'กิจกรรมล่าสุด', + 'create_now' => 'สร้างตอนนี้', + 'revisions' => 'การแก้ไข', + 'meta_revision' => 'การแก้ไข #:revisionCount', + 'meta_created' => 'สร้างเมื่อ :timeLength', + 'meta_created_name' => 'สร้างเมื่อ :timeLength โดย :user', + 'meta_updated' => 'แก้ไขเมื่อ :timeLength', + 'meta_updated_name' => 'แก้ไขเมื่อ :timeLength โดย :user', + 'meta_owned_name' => 'เป็นของ :user', + 'meta_reference_count' => 'อ้างอิงโดย :count รายการ', + 'entity_select' => 'เลือกรายการ', + 'entity_select_lack_permission' => 'คุณไม่มีสิทธิ์เลือกรายการนี้', + 'images' => 'รูปภาพ', + 'my_recent_drafts' => 'ร่างล่าสุดของฉัน', + 'my_recently_viewed' => 'ที่ฉันดูล่าสุด', + 'my_most_viewed_favourites' => 'รายการโปรดที่ดูบ่อยที่สุด', + 'my_favourites' => 'รายการโปรดของฉัน', + 'no_pages_viewed' => 'คุณยังไม่ได้ดูหน้าใด', + 'no_pages_recently_created' => 'ยังไม่มีหน้าที่สร้างล่าสุด', + 'no_pages_recently_updated' => 'ยังไม่มีหน้าที่แก้ไขล่าสุด', + 'export' => 'ส่งออก', + 'export_html' => 'ไฟล์เว็บ (HTML)', + 'export_pdf' => 'ไฟล์ PDF', + 'export_text' => 'ไฟล์ข้อความธรรมดา', + 'export_md' => 'ไฟล์ Markdown', + 'export_zip' => 'ZIP แบบพกพา', + 'default_template' => 'แม่แบบหน้าเริ่มต้น', + 'default_template_explain' => 'กำหนดแม่แบบหน้าที่จะใช้เป็นเนื้อหาเริ่มต้นสำหรับหน้าที่สร้างในรายการนี้ โปรดทราบว่าจะใช้งานได้เฉพาะเมื่อผู้สร้างหน้ามีสิทธิ์ดูแม่แบบที่เลือก', + 'default_template_select' => 'เลือกแม่แบบหน้า', + 'import' => 'นำเข้า', + 'import_validate' => 'ตรวจสอบการนำเข้า', + 'import_desc' => 'นำเข้าหนังสือ บท และหน้าจากไฟล์ ZIP แบบพกพาจากระบบเดียวกันหรือต่างระบบ เลือกไฟล์ ZIP เพื่อดำเนินการต่อ หลังจากอัปโหลดและตรวจสอบแล้ว คุณจะสามารถกำหนดค่าและยืนยันการนำเข้าในขั้นตอนถัดไป', + 'import_zip_select' => 'เลือกไฟล์ ZIP ที่จะอัปโหลด', + 'import_zip_validation_errors' => 'พบข้อผิดพลาดขณะตรวจสอบไฟล์ ZIP:', + 'import_pending' => 'การนำเข้าที่รอดำเนินการ', + 'import_pending_none' => 'ยังไม่มีการนำเข้าที่เริ่มไว้', + 'import_continue' => 'ดำเนินการนำเข้าต่อ', + 'import_continue_desc' => 'ตรวจสอบเนื้อหาที่จะนำเข้าจากไฟล์ ZIP เมื่อพร้อมแล้ว ให้รันการนำเข้าเพื่อเพิ่มเนื้อหาเข้าสู่ระบบ ไฟล์ ZIP จะถูกลบโดยอัตโนมัติเมื่อนำเข้าสำเร็จ', + 'import_details' => 'รายละเอียดการนำเข้า', + 'import_run' => 'รันการนำเข้า', + 'import_size' => 'ขนาดไฟล์ ZIP :size', + 'import_uploaded_at' => 'อัปโหลดเมื่อ :relativeTime', + 'import_uploaded_by' => 'อัปโหลดโดย', + 'import_location' => 'ตำแหน่งนำเข้า', + 'import_location_desc' => 'เลือกตำแหน่งปลายทางสำหรับเนื้อหาที่นำเข้า คุณต้องมีสิทธิ์สร้างเนื้อหาในตำแหน่งที่เลือก', + 'import_delete_confirm' => 'คุณแน่ใจหรือไม่ว่าต้องการลบการนำเข้านี้?', + 'import_delete_desc' => 'การดำเนินการนี้จะลบไฟล์ ZIP ที่อัปโหลดไว้และไม่สามารถยกเลิกได้', + 'import_errors' => 'ข้อผิดพลาดในการนำเข้า', + 'import_errors_desc' => 'เกิดข้อผิดพลาดต่อไปนี้ระหว่างการนำเข้า:', + 'breadcrumb_siblings_for_page' => 'นำทางไปยังหน้าที่อยู่ระดับเดียวกัน', + 'breadcrumb_siblings_for_chapter' => 'นำทางไปยังบทที่อยู่ระดับเดียวกัน', + 'breadcrumb_siblings_for_book' => 'นำทางไปยังหนังสือที่อยู่ระดับเดียวกัน', + 'breadcrumb_siblings_for_bookshelf' => 'นำทางไปยังชั้นวางที่อยู่ระดับเดียวกัน', + + // Permissions and restrictions + 'permissions' => 'สิทธิ์', + 'permissions_desc' => 'ตั้งค่าสิทธิ์ที่นี่เพื่อแทนที่สิทธิ์เริ่มต้นที่กำหนดโดยบทบาทผู้ใช้', + 'permissions_book_cascade' => 'สิทธิ์ที่กำหนดบนหนังสือจะส่งต่อไปยังบทและหน้าลูกโดยอัตโนมัติ เว้นแต่มีการกำหนดสิทธิ์ของตัวเองไว้', + 'permissions_chapter_cascade' => 'สิทธิ์ที่กำหนดบนบทจะส่งต่อไปยังหน้าลูกโดยอัตโนมัติ เว้นแต่มีการกำหนดสิทธิ์ของตัวเองไว้', + 'permissions_save' => 'บันทึกสิทธิ์', + 'permissions_owner' => 'เจ้าของ', + 'permissions_role_everyone_else' => 'ทุกคนที่เหลือ', + 'permissions_role_everyone_else_desc' => 'ตั้งค่าสิทธิ์สำหรับบทบาทที่ไม่ได้กำหนดไว้โดยเฉพาะ', + 'permissions_role_override' => 'แทนที่สิทธิ์สำหรับบทบาท', + 'permissions_inherit_defaults' => 'รับสิทธิ์เริ่มต้น', + + // Search + 'search_results' => 'ผลการค้นหา', + 'search_total_results_found' => 'พบ :count ผลลัพธ์', + 'search_clear' => 'ล้างการค้นหา', + 'search_no_pages' => 'ไม่มีหน้าที่ตรงกับการค้นหานี้', + 'search_for_term' => 'ค้นหา :term', + 'search_more' => 'ผลลัพธ์เพิ่มเติม', + 'search_advanced' => 'ค้นหาขั้นสูง', + 'search_terms' => 'คำค้นหา', + 'search_content_type' => 'ประเภทเนื้อหา', + 'search_exact_matches' => 'ตรงทั้งหมด', + 'search_tags' => 'ค้นหาด้วยแท็ก', + 'search_options' => 'ตัวเลือก', + 'search_viewed_by_me' => 'ที่ฉันดูแล้ว', + 'search_not_viewed_by_me' => 'ที่ฉันยังไม่ได้ดู', + 'search_permissions_set' => 'มีการกำหนดสิทธิ์', + 'search_created_by_me' => 'ที่ฉันสร้าง', + 'search_updated_by_me' => 'ที่ฉันแก้ไข', + 'search_owned_by_me' => 'ที่ฉันเป็นเจ้าของ', + 'search_date_options' => 'ตัวเลือกวันที่', + 'search_updated_before' => 'แก้ไขก่อน', + 'search_updated_after' => 'แก้ไขหลัง', + 'search_created_before' => 'สร้างก่อน', + 'search_created_after' => 'สร้างหลัง', + 'search_set_date' => 'กำหนดวันที่', + 'search_update' => 'อัปเดตการค้นหา', + + // Shelves + 'shelf' => 'ชั้นวาง', + 'shelves' => 'ชั้นวาง', + 'x_shelves' => ':count ชั้นวาง', + 'shelves_empty' => 'ยังไม่มีชั้นวาง', + 'shelves_create' => 'สร้างชั้นวางใหม่', + 'shelves_popular' => 'ชั้นวางยอดนิยม', + 'shelves_new' => 'ชั้นวางใหม่', + 'shelves_new_action' => 'ชั้นวางใหม่', + 'shelves_popular_empty' => 'ชั้นวางที่ได้รับความนิยมมากที่สุดจะแสดงที่นี่', + 'shelves_new_empty' => 'ชั้นวางที่สร้างล่าสุดจะแสดงที่นี่', + 'shelves_save' => 'บันทึกชั้นวาง', + 'shelves_books' => 'หนังสือในชั้นวางนี้', + 'shelves_add_books' => 'เพิ่มหนังสือในชั้นวางนี้', + 'shelves_drag_books' => 'ลากหนังสือด้านล่างเพื่อเพิ่มในชั้นวางนี้', + 'shelves_empty_contents' => 'ชั้นวางนี้ยังไม่มีหนังสือ', + 'shelves_edit_and_assign' => 'แก้ไขชั้นวางเพื่อกำหนดหนังสือ', + 'shelves_edit_named' => 'แก้ไขชั้นวาง :name', + 'shelves_edit' => 'แก้ไขชั้นวาง', + 'shelves_delete' => 'ลบชั้นวาง', + 'shelves_delete_named' => 'ลบชั้นวาง :name', + 'shelves_delete_explain' => "การดำเนินการนี้จะลบชั้นวางชื่อ ':name' หนังสือภายในจะไม่ถูกลบ", + 'shelves_delete_confirmation' => 'คุณแน่ใจหรือไม่ว่าต้องการลบชั้นวางนี้?', + 'shelves_permissions' => 'สิทธิ์ชั้นวาง', + 'shelves_permissions_updated' => 'อัปเดตสิทธิ์ชั้นวางแล้ว', + 'shelves_permissions_active' => 'สิทธิ์ชั้นวางเปิดใช้งานอยู่', + 'shelves_permissions_cascade_warning' => 'สิทธิ์บนชั้นวางไม่ส่งต่อไปยังหนังสือภายในโดยอัตโนมัติ เนื่องจากหนังสือสามารถอยู่ในหลายชั้นวางได้ อย่างไรก็ตาม สามารถคัดลอกสิทธิ์ไปยังหนังสือลูกได้โดยใช้ตัวเลือกด้านล่าง', + 'shelves_permissions_create' => 'สิทธิ์สร้างชั้นวางจะใช้สำหรับการคัดลอกสิทธิ์ไปยังหนังสือลูกเท่านั้น ไม่ได้ควบคุมความสามารถในการสร้างหนังสือ', + 'shelves_copy_permissions_to_books' => 'คัดลอกสิทธิ์ไปยังหนังสือ', + 'shelves_copy_permissions' => 'คัดลอกสิทธิ์', + 'shelves_copy_permissions_explain' => 'การดำเนินการนี้จะนำการตั้งค่าสิทธิ์ปัจจุบันของชั้นวางนี้ไปใช้กับหนังสือทั้งหมดภายใน ก่อนเริ่มต้น ให้ตรวจสอบว่าบันทึกการเปลี่ยนแปลงสิทธิ์แล้ว', + 'shelves_copy_permission_success' => 'คัดลอกสิทธิ์ชั้นวางไปยัง :count หนังสือแล้ว', + + // Books + 'book' => 'หนังสือ', + 'books' => 'หนังสือ', + 'x_books' => ':count หนังสือ', + 'books_empty' => 'ยังไม่มีหนังสือ', + 'books_popular' => 'หนังสือยอดนิยม', + 'books_recent' => 'หนังสือล่าสุด', + 'books_new' => 'หนังสือใหม่', + 'books_new_action' => 'หนังสือใหม่', + 'books_popular_empty' => 'หนังสือที่ได้รับความนิยมมากที่สุดจะแสดงที่นี่', + 'books_new_empty' => 'หนังสือที่สร้างล่าสุดจะแสดงที่นี่', + 'books_create' => 'สร้างหนังสือใหม่', + 'books_delete' => 'ลบหนังสือ', + 'books_delete_named' => 'ลบหนังสือ :bookName', + 'books_delete_explain' => 'การดำเนินการนี้จะลบหนังสือชื่อ \':bookName\' หน้าและบทภายในทั้งหมดจะถูกลบด้วย', + 'books_delete_confirmation' => 'คุณแน่ใจหรือไม่ว่าต้องการลบหนังสือนี้?', + 'books_edit' => 'แก้ไขหนังสือ', + 'books_edit_named' => 'แก้ไขหนังสือ :bookName', + 'books_form_book_name' => 'ชื่อหนังสือ', + 'books_save' => 'บันทึกหนังสือ', + 'books_permissions' => 'สิทธิ์หนังสือ', + 'books_permissions_updated' => 'อัปเดตสิทธิ์หนังสือแล้ว', + 'books_empty_contents' => 'ยังไม่มีหน้าหรือบทในหนังสือนี้', + 'books_empty_create_page' => 'สร้างหน้าใหม่', + 'books_empty_sort_current_book' => 'จัดเรียงหนังสือนี้', + 'books_empty_add_chapter' => 'เพิ่มบท', + 'books_permissions_active' => 'สิทธิ์หนังสือเปิดใช้งานอยู่', + 'books_search_this' => 'ค้นหาในหนังสือนี้', + 'books_navigation' => 'การนำทางหนังสือ', + 'books_sort' => 'จัดเรียงเนื้อหาหนังสือ', + 'books_sort_desc' => 'ย้ายบทและหน้าภายในหนังสือเพื่อจัดเรียงใหม่ สามารถเพิ่มหนังสือเล่มอื่นเพื่อย้ายบทและหน้าระหว่างหนังสือได้ อาจตั้งกฎการจัดเรียงอัตโนมัติเพื่อจัดเรียงเนื้อหาโดยอัตโนมัติเมื่อมีการเปลี่ยนแปลง', + 'books_sort_auto_sort' => 'ตัวเลือกการจัดเรียงอัตโนมัติ', + 'books_sort_auto_sort_active' => 'การจัดเรียงอัตโนมัติเปิดใช้งาน: :sortName', + 'books_sort_auto_sort_creation_hint' => 'กฎการจัดเรียงอัตโนมัติสามารถสร้างได้ในหน้าตั้งค่า "รายการและการจัดเรียง" โดยผู้ใช้ที่มีสิทธิ์ที่เกี่ยวข้อง', + 'books_sort_named' => 'จัดเรียงหนังสือ :bookName', + 'books_sort_name' => 'จัดเรียงตามชื่อ', + 'books_sort_created' => 'จัดเรียงตามวันที่สร้าง', + 'books_sort_updated' => 'จัดเรียงตามวันที่แก้ไข', + 'books_sort_chapters_first' => 'บทอยู่ก่อน', + 'books_sort_chapters_last' => 'บทอยู่หลัง', + 'books_sort_show_other' => 'แสดงหนังสือเล่มอื่น', + 'books_sort_save' => 'บันทึกลำดับใหม่', + 'books_sort_show_other_desc' => 'เพิ่มหนังสือเล่มอื่นที่นี่เพื่อรวมในการจัดเรียง และอนุญาตให้จัดระเบียบข้ามหนังสือได้ง่าย', + 'books_sort_move_up' => 'เลื่อนขึ้น', + 'books_sort_move_down' => 'เลื่อนลง', + 'books_sort_move_prev_book' => 'ย้ายไปหนังสือก่อนหน้า', + 'books_sort_move_next_book' => 'ย้ายไปหนังสือถัดไป', + 'books_sort_move_prev_chapter' => 'ย้ายเข้าบทก่อนหน้า', + 'books_sort_move_next_chapter' => 'ย้ายเข้าบทถัดไป', + 'books_sort_move_book_start' => 'ย้ายไปต้นหนังสือ', + 'books_sort_move_book_end' => 'ย้ายไปท้ายหนังสือ', + 'books_sort_move_before_chapter' => 'ย้ายไปก่อนบท', + 'books_sort_move_after_chapter' => 'ย้ายไปหลังบท', + 'books_copy' => 'คัดลอกหนังสือ', + 'books_copy_success' => 'คัดลอกหนังสือสำเร็จแล้ว', + + // Chapters + 'chapter' => 'บท', + 'chapters' => 'บท', + 'x_chapters' => ':count บท', + 'chapters_popular' => 'บทยอดนิยม', + 'chapters_new' => 'บทใหม่', + 'chapters_create' => 'สร้างบทใหม่', + 'chapters_delete' => 'ลบบท', + 'chapters_delete_named' => 'ลบบท :chapterName', + 'chapters_delete_explain' => 'การดำเนินการนี้จะลบบทชื่อ \':chapterName\' หน้าทั้งหมดในบทนี้จะถูกลบด้วย', + 'chapters_delete_confirm' => 'คุณแน่ใจหรือไม่ว่าต้องการลบบทนี้?', + 'chapters_edit' => 'แก้ไขบท', + 'chapters_edit_named' => 'แก้ไขบท :chapterName', + 'chapters_save' => 'บันทึกบท', + 'chapters_move' => 'ย้ายบท', + 'chapters_move_named' => 'ย้ายบท :chapterName', + 'chapters_copy' => 'คัดลอกบท', + 'chapters_copy_success' => 'คัดลอกบทสำเร็จแล้ว', + 'chapters_permissions' => 'สิทธิ์บท', + 'chapters_empty' => 'ยังไม่มีหน้าในบทนี้', + 'chapters_permissions_active' => 'สิทธิ์บทเปิดใช้งานอยู่', + 'chapters_permissions_success' => 'อัปเดตสิทธิ์บทแล้ว', + 'chapters_search_this' => 'ค้นหาในบทนี้', + 'chapter_sort_book' => 'จัดเรียงหนังสือ', + + // Pages + 'page' => 'หน้า', + 'pages' => 'หน้า', + 'x_pages' => ':count หน้า', + 'pages_popular' => 'หน้ายอดนิยม', + 'pages_new' => 'หน้าใหม่', + 'pages_attachments' => 'ไฟล์แนบ', + 'pages_navigation' => 'การนำทางหน้า', + 'pages_delete' => 'ลบหน้า', + 'pages_delete_named' => 'ลบหน้า :pageName', + 'pages_delete_draft_named' => 'ลบร่างหน้า :pageName', + 'pages_delete_draft' => 'ลบร่างหน้า', + 'pages_delete_success' => 'ลบหน้าแล้ว', + 'pages_delete_draft_success' => 'ลบร่างหน้าแล้ว', + 'pages_delete_warning_template' => 'หน้านี้ถูกใช้งานเป็นแม่แบบหน้าเริ่มต้นของหนังสือหรือบท หลังจากลบหน้านี้ หนังสือหรือบทดังกล่าวจะไม่มีแม่แบบหน้าเริ่มต้น', + 'pages_delete_confirm' => 'คุณแน่ใจหรือไม่ว่าต้องการลบหน้านี้?', + 'pages_delete_draft_confirm' => 'คุณแน่ใจหรือไม่ว่าต้องการลบร่างหน้านี้?', + 'pages_editing_named' => 'กำลังแก้ไขหน้า :pageName', + 'pages_edit_draft_options' => 'ตัวเลือกร่าง', + 'pages_edit_save_draft' => 'บันทึกร่าง', + 'pages_edit_draft' => 'แก้ไขร่างหน้า', + 'pages_editing_draft' => 'กำลังแก้ไขร่าง', + 'pages_editing_page' => 'กำลังแก้ไขหน้า', + 'pages_edit_draft_save_at' => 'บันทึกร่างเมื่อ ', + 'pages_edit_delete_draft' => 'ลบร่าง', + 'pages_edit_delete_draft_confirm' => 'คุณแน่ใจหรือไม่ว่าต้องการลบร่างหน้านี้? การเปลี่ยนแปลงทั้งหมดตั้งแต่บันทึกครั้งล่าสุดจะสูญหาย และตัวแก้ไขจะโหลดเนื้อหาที่บันทึกล่าสุด', + 'pages_edit_discard_draft' => 'ยกเลิกร่าง', + 'pages_edit_switch_to_markdown' => 'เปลี่ยนไปใช้ตัวแก้ไข Markdown', + 'pages_edit_switch_to_markdown_clean' => '(เนื้อหาสะอาด)', + 'pages_edit_switch_to_markdown_stable' => '(เนื้อหาเสถียร)', + 'pages_edit_switch_to_wysiwyg' => 'เปลี่ยนไปใช้ตัวแก้ไข WYSIWYG', + 'pages_edit_switch_to_new_wysiwyg' => 'เปลี่ยนไปใช้ WYSIWYG ใหม่', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(กำลังทดสอบ Beta)', + 'pages_edit_set_changelog' => 'ตั้งค่า Changelog', + 'pages_edit_enter_changelog_desc' => 'กรอกคำอธิบายสั้นๆ เกี่ยวกับการเปลี่ยนแปลงที่ทำ', + 'pages_edit_enter_changelog' => 'กรอก Changelog', + 'pages_editor_switch_title' => 'เปลี่ยนตัวแก้ไข', + 'pages_editor_switch_are_you_sure' => 'คุณแน่ใจหรือไม่ว่าต้องการเปลี่ยนตัวแก้ไขสำหรับหน้านี้?', + 'pages_editor_switch_consider_following' => 'โปรดพิจารณาสิ่งต่อไปนี้เมื่อเปลี่ยนตัวแก้ไข:', + 'pages_editor_switch_consideration_a' => 'เมื่อบันทึกแล้ว ตัวแก้ไขใหม่จะถูกใช้โดยผู้แก้ไขในอนาคต รวมถึงผู้ที่อาจไม่สามารถเปลี่ยนประเภทตัวแก้ไขได้เอง', + 'pages_editor_switch_consideration_b' => 'อาจทำให้รายละเอียดและไวยากรณ์บางอย่างสูญหายในบางสถานการณ์', + 'pages_editor_switch_consideration_c' => 'การเปลี่ยนแปลงแท็กหรือ changelog ที่ทำตั้งแต่บันทึกครั้งล่าสุดจะไม่ถูกบันทึกหลังจากเปลี่ยนนี้', + 'pages_save' => 'บันทึกหน้า', + 'pages_title' => 'ชื่อหน้า', + 'pages_name' => 'ชื่อหน้า', + 'pages_md_editor' => 'ตัวแก้ไข', + 'pages_md_preview' => 'ดูตัวอย่าง', + 'pages_md_insert_image' => 'แทรกรูปภาพ', + 'pages_md_insert_link' => 'แทรกลิงก์รายการ', + 'pages_md_insert_drawing' => 'แทรกภาพวาด', + 'pages_md_show_preview' => 'แสดงตัวอย่าง', + 'pages_md_sync_scroll' => 'ซิงค์การเลื่อนหน้าตัวอย่าง', + 'pages_md_plain_editor' => 'ตัวแก้ไขข้อความธรรมดา', + 'pages_drawing_unsaved' => 'พบภาพวาดที่ยังไม่บันทึก', + 'pages_drawing_unsaved_confirm' => 'พบข้อมูลภาพวาดที่ยังไม่บันทึกจากการบันทึกที่ล้มเหลวก่อนหน้า ต้องการกู้คืนและแก้ไขภาพวาดที่ยังไม่บันทึกนี้ต่อหรือไม่?', + 'pages_not_in_chapter' => 'หน้านี้ไม่ได้อยู่ในบท', + 'pages_move' => 'ย้ายหน้า', + 'pages_copy' => 'คัดลอกหน้า', + 'pages_copy_desination' => 'ปลายทางการคัดลอก', + 'pages_copy_success' => 'คัดลอกหน้าสำเร็จแล้ว', + 'pages_permissions' => 'สิทธิ์หน้า', + 'pages_permissions_success' => 'อัปเดตสิทธิ์หน้าแล้ว', + 'pages_revision' => 'การแก้ไข', + 'pages_revisions' => 'การแก้ไขหน้า', + 'pages_revisions_desc' => 'แสดงรายการการแก้ไขทั้งหมดของหน้านี้ คุณสามารถดูย้อนหลัง เปรียบเทียบ และกู้คืนเวอร์ชันเก่าได้หากมีสิทธิ์ ประวัติทั้งหมดอาจไม่แสดงครบเนื่องจากการตั้งค่าระบบอาจลบการแก้ไขเก่าโดยอัตโนมัติ', + 'pages_revisions_named' => 'การแก้ไขหน้าสำหรับ :pageName', + 'pages_revision_named' => 'การแก้ไขหน้าสำหรับ :pageName', + 'pages_revision_restored_from' => 'กู้คืนจาก #:id; :summary', + 'pages_revisions_created_by' => 'สร้างโดย', + 'pages_revisions_date' => 'วันที่แก้ไข', + 'pages_revisions_number' => '#', + 'pages_revisions_sort_number' => 'หมายเลขการแก้ไข', + 'pages_revisions_numbered' => 'การแก้ไข #:id', + 'pages_revisions_numbered_changes' => 'การเปลี่ยนแปลงในการแก้ไข #:id', + 'pages_revisions_editor' => 'ประเภทตัวแก้ไข', + 'pages_revisions_changelog' => 'Changelog', + 'pages_revisions_changes' => 'การเปลี่ยนแปลง', + 'pages_revisions_current' => 'เวอร์ชันปัจจุบัน', + 'pages_revisions_preview' => 'ดูตัวอย่าง', + 'pages_revisions_restore' => 'กู้คืน', + 'pages_revisions_none' => 'หน้านี้ยังไม่มีการแก้ไข', + 'pages_copy_link' => 'คัดลอกลิงก์', + 'pages_edit_content_link' => 'ข้ามไปยังส่วนในตัวแก้ไข', + 'pages_pointer_enter_mode' => 'เข้าสู่โหมดเลือกส่วน', + 'pages_pointer_label' => 'ตัวเลือกส่วนหน้า', + 'pages_pointer_permalink' => 'ลิงก์ถาวรส่วนหน้า', + 'pages_pointer_include_tag' => 'แท็กรวมส่วนหน้า', + 'pages_pointer_toggle_link' => 'โหมดลิงก์ถาวร กดเพื่อแสดงแท็กรวม', + 'pages_pointer_toggle_include' => 'โหมดแท็กรวม กดเพื่อแสดงลิงก์ถาวร', + 'pages_permissions_active' => 'สิทธิ์หน้าเปิดใช้งานอยู่', + 'pages_initial_revision' => 'เผยแพร่ครั้งแรก', + 'pages_references_update_revision' => 'ระบบอัปเดตลิงก์ภายในอัตโนมัติ', + 'pages_initial_name' => 'หน้าใหม่', + 'pages_editing_draft_notification' => 'คุณกำลังแก้ไขร่างที่บันทึกล่าสุดเมื่อ :timeDiff', + 'pages_draft_edited_notification' => 'หน้านี้ได้รับการอัปเดตตั้งแต่นั้นมา แนะนำให้ยกเลิกร่างนี้', + 'pages_draft_page_changed_since_creation' => 'หน้านี้ได้รับการอัปเดตตั้งแต่สร้างร่างนี้ แนะนำให้ยกเลิกร่างหรือระมัดระวังไม่ให้เขียนทับการเปลี่ยนแปลงของหน้า', + 'pages_draft_edit_active' => [ + 'start_a' => 'มีผู้ใช้ :count คนกำลังแก้ไขหน้านี้', + 'start_b' => ':userName กำลังแก้ไขหน้านี้', + 'time_a' => 'ตั้งแต่อัปเดตหน้าล่าสุด', + 'time_b' => 'ใน :minCount นาทีที่ผ่านมา', + 'message' => ':start :time โปรดระวังอย่าเขียนทับการแก้ไขของกันและกัน!', + ], + 'pages_draft_discarded' => 'ยกเลิกร่างแล้ว! ตัวแก้ไขได้รับการอัปเดตด้วยเนื้อหาปัจจุบันของหน้า', + 'pages_draft_deleted' => 'ลบร่างแล้ว! ตัวแก้ไขได้รับการอัปเดตด้วยเนื้อหาปัจจุบันของหน้า', + 'pages_specific' => 'หน้าเฉพาะ', + 'pages_is_template' => 'แม่แบบหน้า', + + // Editor Sidebar + 'toggle_sidebar' => 'แสดง/ซ่อนแถบด้านข้าง', + 'page_tags' => 'แท็กหน้า', + 'chapter_tags' => 'แท็กบท', + 'book_tags' => 'แท็กหนังสือ', + 'shelf_tags' => 'แท็กชั้นวาง', + 'tag' => 'แท็ก', + 'tags' => 'แท็ก', + 'tags_index_desc' => 'แท็กสามารถนำไปใช้กับเนื้อหาในระบบเพื่อจัดหมวดหมู่แบบยืดหยุ่น แท็กสามารถมีทั้งคีย์และค่า โดยค่าไม่บังคับ เมื่อกำหนดแล้ว สามารถค้นหาเนื้อหาด้วยชื่อและค่าของแท็กได้', + 'tag_name' => 'ชื่อแท็ก', + 'tag_value' => 'ค่าแท็ก (ไม่บังคับ)', + 'tags_explain' => "เพิ่มแท็กเพื่อจัดหมวดหมู่เนื้อหาให้ดีขึ้น \n คุณสามารถกำหนดค่าให้แท็กเพื่อการจัดระเบียบที่ละเอียดขึ้น", + 'tags_add' => 'เพิ่มแท็ก', + 'tags_remove' => 'ลบแท็กนี้', + 'tags_usages' => 'การใช้งานแท็กทั้งหมด', + 'tags_assigned_pages' => 'กำหนดให้หน้า', + 'tags_assigned_chapters' => 'กำหนดให้บท', + 'tags_assigned_books' => 'กำหนดให้หนังสือ', + 'tags_assigned_shelves' => 'กำหนดให้ชั้นวาง', + 'tags_x_unique_values' => ':count ค่าที่ไม่ซ้ำ', + 'tags_all_values' => 'ค่าทั้งหมด', + 'tags_view_tags' => 'ดูแท็ก', + 'tags_view_existing_tags' => 'ดูแท็กที่มีอยู่', + 'tags_list_empty_hint' => 'แท็กสามารถกำหนดได้ผ่านแถบด้านข้างของตัวแก้ไขหน้า หรือขณะแก้ไขรายละเอียดของหนังสือ บท หรือชั้นวาง', + 'attachments' => 'ไฟล์แนบ', + 'attachments_explain' => 'อัปโหลดไฟล์หรือแนบลิงก์เพื่อแสดงในหน้านี้ จะมองเห็นได้ในแถบด้านข้างของหน้า', + 'attachments_explain_instant_save' => 'การเปลี่ยนแปลงที่นี่จะบันทึกทันที', + 'attachments_upload' => 'อัปโหลดไฟล์', + 'attachments_link' => 'แนบลิงก์', + 'attachments_upload_drop' => 'หรือคุณสามารถลากและวางไฟล์ที่นี่เพื่ออัปโหลดเป็นไฟล์แนบ', + 'attachments_set_link' => 'ตั้งค่าลิงก์', + 'attachments_delete' => 'คุณแน่ใจหรือไม่ว่าต้องการลบไฟล์แนบนี้?', + 'attachments_dropzone' => 'วางไฟล์ที่นี่เพื่ออัปโหลด', + 'attachments_no_files' => 'ยังไม่มีไฟล์ที่อัปโหลด', + 'attachments_explain_link' => 'คุณสามารถแนบลิงก์แทนการอัปโหลดไฟล์ได้ ลิงก์อาจเป็นลิงก์ไปยังหน้าอื่นหรือไฟล์ในคลาวด์', + 'attachments_link_name' => 'ชื่อลิงก์', + 'attachment_link' => 'ลิงก์ไฟล์แนบ', + 'attachments_link_url' => 'ลิงก์ไปยังไฟล์', + 'attachments_link_url_hint' => 'URL ของเว็บไซต์หรือไฟล์', + 'attach' => 'แนบ', + 'attachments_insert_link' => 'เพิ่มลิงก์ไฟล์แนบในหน้า', + 'attachments_edit_file' => 'แก้ไขไฟล์', + 'attachments_edit_file_name' => 'ชื่อไฟล์', + 'attachments_edit_drop_upload' => 'วางไฟล์หรือคลิกที่นี่เพื่ออัปโหลดและแทนที่', + 'attachments_order_updated' => 'อัปเดตลำดับไฟล์แนบแล้ว', + 'attachments_updated_success' => 'อัปเดตรายละเอียดไฟล์แนบแล้ว', + 'attachments_deleted' => 'ลบไฟล์แนบแล้ว', + 'attachments_file_uploaded' => 'อัปโหลดไฟล์สำเร็จแล้ว', + 'attachments_file_updated' => 'อัปเดตไฟล์สำเร็จแล้ว', + 'attachments_link_attached' => 'แนบลิงก์ไปยังหน้าสำเร็จแล้ว', + 'templates' => 'แม่แบบ', + 'templates_set_as_template' => 'หน้านี้เป็นแม่แบบ', + 'templates_explain_set_as_template' => 'คุณสามารถตั้งหน้านี้เป็นแม่แบบเพื่อให้นำเนื้อหาไปใช้เมื่อสร้างหน้าอื่น ผู้ใช้อื่นจะสามารถใช้แม่แบบนี้ได้หากมีสิทธิ์ดูหน้านี้', + 'templates_replace_content' => 'แทนที่เนื้อหาหน้า', + 'templates_append_content' => 'ต่อท้ายเนื้อหาหน้า', + 'templates_prepend_content' => 'เพิ่มก่อนเนื้อหาหน้า', + + // Profile View + 'profile_user_for_x' => 'ผู้ใช้มาแล้ว :time', + 'profile_created_content' => 'เนื้อหาที่สร้าง', + 'profile_not_created_pages' => ':userName ยังไม่ได้สร้างหน้าใด', + 'profile_not_created_chapters' => ':userName ยังไม่ได้สร้างบทใด', + 'profile_not_created_books' => ':userName ยังไม่ได้สร้างหนังสือใด', + 'profile_not_created_shelves' => ':userName ยังไม่ได้สร้างชั้นวางใด', + + // Comments + 'comment' => 'ความคิดเห็น', + 'comments' => 'ความคิดเห็น', + 'comment_add' => 'เพิ่มความคิดเห็น', + 'comment_none' => 'ไม่มีความคิดเห็น', + 'comment_placeholder' => 'เขียนความคิดเห็นที่นี่', + 'comment_thread_count' => ':count เธรดความคิดเห็น', + 'comment_archived_count' => ':count ที่เก็บถาวร', + 'comment_archived_threads' => 'เธรดที่เก็บถาวร', + 'comment_save' => 'บันทึกความคิดเห็น', + 'comment_new' => 'ความคิดเห็นใหม่', + 'comment_created' => 'แสดงความคิดเห็นเมื่อ :createDiff', + 'comment_updated' => 'แก้ไขเมื่อ :updateDiff โดย :username', + 'comment_updated_indicator' => 'แก้ไขแล้ว', + 'comment_deleted_success' => 'ลบความคิดเห็นแล้ว', + 'comment_created_success' => 'เพิ่มความคิดเห็นแล้ว', + 'comment_updated_success' => 'แก้ไขความคิดเห็นแล้ว', + 'comment_archive_success' => 'เก็บถาวรความคิดเห็นแล้ว', + 'comment_unarchive_success' => 'ยกเลิกการเก็บถาวรความคิดเห็นแล้ว', + 'comment_view' => 'ดูความคิดเห็น', + 'comment_jump_to_thread' => 'ไปยังเธรด', + 'comment_delete_confirm' => 'คุณแน่ใจหรือไม่ว่าต้องการลบความคิดเห็นนี้?', + 'comment_in_reply_to' => 'ตอบกลับ :commentId', + 'comment_reference' => 'อ้างอิง', + 'comment_reference_outdated' => '(ล้าสมัย)', + 'comment_editor_explain' => 'แสดงความคิดเห็นที่มีในหน้านี้ สามารถเพิ่มและจัดการความคิดเห็นได้เมื่อดูหน้าที่บันทึกแล้ว', + + // Revision + 'revision_delete_confirm' => 'คุณแน่ใจหรือไม่ว่าต้องการลบการแก้ไขนี้?', + 'revision_restore_confirm' => 'คุณแน่ใจหรือไม่ว่าต้องการกู้คืนการแก้ไขนี้? เนื้อหาปัจจุบันของหน้าจะถูกแทนที่', + 'revision_cannot_delete_latest' => 'ไม่สามารถลบการแก้ไขล่าสุดได้', + + // Copy view + 'copy_consider' => 'โปรดพิจารณาสิ่งต่อไปนี้เมื่อคัดลอกเนื้อหา', + 'copy_consider_permissions' => 'การตั้งค่าสิทธิ์แบบกำหนดเองจะไม่ถูกคัดลอก', + 'copy_consider_owner' => 'คุณจะเป็นเจ้าของเนื้อหาที่คัดลอกทั้งหมด', + 'copy_consider_images' => 'ไฟล์รูปภาพในหน้าจะไม่ถูกทำสำเนา และรูปภาพต้นฉบับจะยังคงเชื่อมกับหน้าที่อัปโหลดไว้เดิม', + 'copy_consider_attachments' => 'ไฟล์แนบในหน้าจะไม่ถูกคัดลอก', + 'copy_consider_access' => 'การเปลี่ยนตำแหน่ง เจ้าของ หรือสิทธิ์อาจทำให้เนื้อหานี้เข้าถึงได้โดยผู้ที่ไม่เคยมีสิทธิ์มาก่อน', + + // Conversions + 'convert_to_shelf' => 'แปลงเป็นชั้นวาง', + 'convert_to_shelf_contents_desc' => 'คุณสามารถแปลงหนังสือนี้เป็นชั้นวางใหม่ที่มีเนื้อหาเดิม บทในหนังสือนี้จะถูกแปลงเป็นหนังสือใหม่ หากหนังสือนี้มีหน้าที่ไม่อยู่ในบท หนังสือนี้จะถูกเปลี่ยนชื่อและเก็บหน้าเหล่านั้น และจะกลายเป็นส่วนหนึ่งของชั้นวางใหม่', + 'convert_to_shelf_permissions_desc' => 'สิทธิ์ที่กำหนดบนหนังสือนี้จะถูกคัดลอกไปยังชั้นวางใหม่และหนังสือลูกทั้งหมดที่ไม่มีสิทธิ์ของตัวเอง โปรดทราบว่าสิทธิ์บนชั้นวางไม่ส่งต่อไปยังเนื้อหาภายในโดยอัตโนมัติเหมือนกับหนังสือ', + 'convert_book' => 'แปลงหนังสือ', + 'convert_book_confirm' => 'คุณแน่ใจหรือไม่ว่าต้องการแปลงหนังสือนี้?', + 'convert_undo_warning' => 'การดำเนินการนี้ยากที่จะยกเลิก', + 'convert_to_book' => 'แปลงเป็นหนังสือ', + 'convert_to_book_desc' => 'คุณสามารถแปลงบทนี้เป็นหนังสือใหม่ที่มีเนื้อหาเดิม สิทธิ์ที่กำหนดบนบทนี้จะถูกคัดลอกไปยังหนังสือใหม่ แต่สิทธิ์ที่รับมาจากหนังสือแม่จะไม่ถูกคัดลอก ซึ่งอาจทำให้การควบคุมการเข้าถึงเปลี่ยนแปลงได้', + 'convert_chapter' => 'แปลงบท', + 'convert_chapter_confirm' => 'คุณแน่ใจหรือไม่ว่าต้องการแปลงบทนี้?', + + // References + 'references' => 'การอ้างอิง', + 'references_none' => 'ยังไม่มีการอ้างอิงที่ติดตามไปยังรายการนี้', + 'references_to_desc' => 'แสดงรายการเนื้อหาทั้งหมดในระบบที่ลิงก์ไปยังรายการนี้', + + // Watch Options + 'watch' => 'ติดตาม', + 'watch_title_default' => 'การตั้งค่าเริ่มต้น', + 'watch_desc_default' => 'กลับไปใช้การตั้งค่าการแจ้งเตือนเริ่มต้นของคุณ', + 'watch_title_ignore' => 'ไม่สนใจ', + 'watch_desc_ignore' => 'ไม่รับการแจ้งเตือนใดๆ รวมถึงจากการตั้งค่าระดับผู้ใช้', + 'watch_title_new' => 'หน้าใหม่', + 'watch_desc_new' => 'แจ้งเตือนเมื่อมีการสร้างหน้าใหม่ในรายการนี้', + 'watch_title_updates' => 'การอัปเดตหน้าทั้งหมด', + 'watch_desc_updates' => 'แจ้งเตือนเมื่อมีหน้าใหม่และการแก้ไขหน้า', + 'watch_desc_updates_page' => 'แจ้งเตือนเมื่อมีการแก้ไขหน้า', + 'watch_title_comments' => 'การอัปเดตและความคิดเห็นทั้งหมด', + 'watch_desc_comments' => 'แจ้งเตือนเมื่อมีหน้าใหม่ การแก้ไขหน้า และความคิดเห็นใหม่', + 'watch_desc_comments_page' => 'แจ้งเตือนเมื่อมีการแก้ไขหน้าและความคิดเห็นใหม่', + 'watch_change_default' => 'เปลี่ยนการตั้งค่าการแจ้งเตือนเริ่มต้น', + 'watch_detail_ignore' => 'ไม่รับการแจ้งเตือน', + 'watch_detail_new' => 'ติดตามหน้าใหม่', + 'watch_detail_updates' => 'ติดตามหน้าใหม่และการอัปเดต', + 'watch_detail_comments' => 'ติดตามหน้าใหม่ การอัปเดต และความคิดเห็น', + 'watch_detail_parent_book' => 'ติดตามผ่านหนังสือแม่', + 'watch_detail_parent_book_ignore' => 'ไม่รับการแจ้งเตือนผ่านหนังสือแม่', + 'watch_detail_parent_chapter' => 'ติดตามผ่านบทแม่', + 'watch_detail_parent_chapter_ignore' => 'ไม่รับการแจ้งเตือนผ่านบทแม่', +]; diff --git a/lang/th/errors.php b/lang/th/errors.php new file mode 100644 index 00000000000..61f1ed82c76 --- /dev/null +++ b/lang/th/errors.php @@ -0,0 +1,135 @@ + 'คุณไม่มีสิทธิ์เข้าถึงหน้าที่ร้องขอ', + 'permissionJson' => 'คุณไม่มีสิทธิ์ดำเนินการที่ร้องขอ', + + // Auth + 'error_user_exists_different_creds' => 'มีผู้ใช้ที่ใช้อีเมล :email อยู่แล้วแต่ใช้ข้อมูลประจำตัวต่างกัน', + 'auth_pre_register_theme_prevention' => 'ไม่สามารถลงทะเบียนบัญชีผู้ใช้สำหรับข้อมูลที่ให้มาได้', + 'email_already_confirmed' => 'ยืนยันอีเมลแล้ว กรุณาลองเข้าสู่ระบบ', + 'email_confirmation_invalid' => 'โทเค็นยืนยันนี้ไม่ถูกต้องหรือถูกใช้ไปแล้ว กรุณาลองลงทะเบียนใหม่', + 'email_confirmation_expired' => 'โทเค็นยืนยันหมดอายุแล้ว ส่งอีเมลยืนยันใหม่ให้แล้ว', + 'email_confirmation_awaiting' => 'ที่อยู่อีเมลของบัญชีที่ใช้งานอยู่ต้องได้รับการยืนยัน', + 'ldap_fail_anonymous' => 'การเข้าถึง LDAP ล้มเหลวโดยใช้การเชื่อมต่อแบบไม่ระบุตัวตน', + 'ldap_fail_authed' => 'การเข้าถึง LDAP ล้มเหลวโดยใช้ข้อมูล dn และรหัสผ่านที่กำหนด', + 'ldap_extension_not_installed' => 'ไม่ได้ติดตั้ง PHP extension สำหรับ LDAP', + 'ldap_cannot_connect' => 'ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ LDAP ได้ การเชื่อมต่อเริ่มต้นล้มเหลว', + 'saml_already_logged_in' => 'เข้าสู่ระบบแล้ว', + 'saml_no_email_address' => 'ไม่พบที่อยู่อีเมลสำหรับผู้ใช้นี้ในข้อมูลที่ระบบยืนยันตัวตนภายนอกส่งมา', + 'saml_invalid_response_id' => 'คำขอจากระบบยืนยันตัวตนภายนอกไม่ได้รับการยอมรับจากกระบวนการที่เริ่มต้นโดยแอปพลิเคชันนี้ การนำทางย้อนกลับหลังเข้าสู่ระบบอาจทำให้เกิดปัญหานี้', + 'saml_fail_authed' => 'การเข้าสู่ระบบด้วย :system ล้มเหลว ระบบไม่ได้ให้การอนุญาตที่สำเร็จ', + 'oidc_already_logged_in' => 'เข้าสู่ระบบแล้ว', + 'oidc_no_email_address' => 'ไม่พบที่อยู่อีเมลสำหรับผู้ใช้นี้ในข้อมูลที่ระบบยืนยันตัวตนภายนอกส่งมา', + 'oidc_fail_authed' => 'การเข้าสู่ระบบด้วย :system ล้มเหลว ระบบไม่ได้ให้การอนุญาตที่สำเร็จ', + 'social_no_action_defined' => 'ไม่ได้กำหนดการดำเนินการ', + 'social_login_bad_response' => "เกิดข้อผิดพลาดระหว่างเข้าสู่ระบบด้วย :socialAccount: \n:error", + 'social_account_in_use' => 'บัญชี :socialAccount นี้ถูกใช้งานแล้ว ลองเข้าสู่ระบบผ่านตัวเลือก :socialAccount', + 'social_account_email_in_use' => 'อีเมล :email ถูกใช้งานแล้ว หากมีบัญชีอยู่แล้ว คุณสามารถเชื่อมต่อบัญชี :socialAccount จากการตั้งค่าโปรไฟล์ได้', + 'social_account_existing' => ':socialAccount นี้เชื่อมต่อกับโปรไฟล์ของคุณแล้ว', + 'social_account_already_used_existing' => 'บัญชี :socialAccount นี้ถูกใช้งานโดยผู้ใช้อื่นแล้ว', + 'social_account_not_used' => 'บัญชี :socialAccount นี้ไม่ได้เชื่อมต่อกับผู้ใช้ใด กรุณาแนบในการตั้งค่าโปรไฟล์', + 'social_account_register_instructions' => 'หากยังไม่มีบัญชี คุณสามารถลงทะเบียนโดยใช้ตัวเลือก :socialAccount', + 'social_driver_not_found' => 'ไม่พบ Social driver', + 'social_driver_not_configured' => 'การตั้งค่า Social ของ :socialAccount ไม่ถูกต้อง', + 'invite_token_expired' => 'ลิงก์เชิญนี้หมดอายุแล้ว คุณสามารถลองรีเซ็ตรหัสผ่านบัญชีแทนได้', + 'login_user_not_found' => 'ไม่พบผู้ใช้สำหรับการดำเนินการนี้', + + // System + 'path_not_writable' => 'ไม่สามารถอัปโหลดไปยังพาธ :filePath ได้ โปรดตรวจสอบว่าเซิร์ฟเวอร์มีสิทธิ์เขียนได้', + 'cannot_get_image_from_url' => 'ไม่สามารถดึงรูปภาพจาก :url ได้', + 'cannot_create_thumbs' => 'เซิร์ฟเวอร์ไม่สามารถสร้างภาพย่อได้ โปรดตรวจสอบว่าติดตั้ง PHP extension GD แล้ว', + 'server_upload_limit' => 'เซิร์ฟเวอร์ไม่อนุญาตให้อัปโหลดไฟล์ขนาดนี้ กรุณาลองใช้ไฟล์ขนาดเล็กกว่า', + 'server_post_limit' => 'เซิร์ฟเวอร์ไม่สามารถรับข้อมูลในปริมาณที่กำหนดได้ ลองใหม่ด้วยข้อมูลน้อยลงหรือไฟล์ขนาดเล็กกว่า', + 'uploaded' => 'เซิร์ฟเวอร์ไม่อนุญาตให้อัปโหลดไฟล์ขนาดนี้ กรุณาลองใช้ไฟล์ขนาดเล็กกว่า', + + // Drawing & Images + 'image_upload_error' => 'เกิดข้อผิดพลาดขณะอัปโหลดรูปภาพ', + 'image_upload_type_error' => 'ประเภทรูปภาพที่อัปโหลดไม่ถูกต้อง', + 'image_upload_replace_type' => 'การแทนที่ไฟล์รูปภาพต้องใช้ประเภทเดียวกัน', + 'image_upload_memory_limit' => 'ไม่สามารถจัดการการอัปโหลดรูปภาพและ/หรือสร้างภาพย่อได้เนื่องจากทรัพยากรระบบไม่เพียงพอ', + 'image_thumbnail_memory_limit' => 'ไม่สามารถสร้างขนาดรูปภาพต่างๆ ได้เนื่องจากทรัพยากรระบบไม่เพียงพอ', + 'image_gallery_thumbnail_memory_limit' => 'ไม่สามารถสร้างภาพย่อแกลเลอรีได้เนื่องจากทรัพยากรระบบไม่เพียงพอ', + 'drawing_data_not_found' => 'ไม่สามารถโหลดข้อมูลภาพวาดได้ ไฟล์ภาพวาดอาจไม่มีอยู่แล้วหรือคุณไม่มีสิทธิ์เข้าถึง', + + // Attachments + 'attachment_not_found' => 'ไม่พบไฟล์แนบ', + 'attachment_upload_error' => 'เกิดข้อผิดพลาดขณะอัปโหลดไฟล์แนบ', + + // Pages + 'page_draft_autosave_fail' => 'บันทึกร่างล้มเหลว โปรดตรวจสอบการเชื่อมต่ออินเทอร์เน็ตก่อนบันทึกหน้านี้', + 'page_draft_delete_fail' => 'ลบร่างหน้าและดึงเนื้อหาที่บันทึกปัจจุบันล้มเหลว', + 'page_custom_home_deletion' => 'ไม่สามารถลบหน้าได้ในขณะที่ตั้งเป็นหน้าแรก', + + // Entities + 'entity_not_found' => 'ไม่พบรายการ', + 'bookshelf_not_found' => 'ไม่พบชั้นวาง', + 'book_not_found' => 'ไม่พบหนังสือ', + 'page_not_found' => 'ไม่พบหน้า', + 'chapter_not_found' => 'ไม่พบบท', + 'selected_book_not_found' => 'ไม่พบหนังสือที่เลือก', + 'selected_book_chapter_not_found' => 'ไม่พบหนังสือหรือบทที่เลือก', + 'guests_cannot_save_drafts' => 'ผู้เยี่ยมชมไม่สามารถบันทึกร่างได้', + + // Users + 'users_cannot_delete_only_admin' => 'ไม่สามารถลบผู้ดูแลระบบคนเดียวได้', + 'users_cannot_delete_guest' => 'ไม่สามารถลบผู้ใช้แบบผู้เยี่ยมชมได้', + 'users_could_not_send_invite' => 'ไม่สามารถสร้างผู้ใช้ได้เนื่องจากส่งอีเมลเชิญล้มเหลว', + + // Roles + 'role_cannot_be_edited' => 'บทบาทนี้ไม่สามารถแก้ไขได้', + 'role_system_cannot_be_deleted' => 'บทบาทนี้เป็นบทบาทระบบและไม่สามารถลบได้', + 'role_registration_default_cannot_delete' => 'บทบาทนี้ไม่สามารถลบได้ในขณะที่ตั้งเป็นบทบาทลงทะเบียนเริ่มต้น', + 'role_cannot_remove_only_admin' => 'ผู้ใช้นี้เป็นผู้ใช้คนเดียวที่ได้รับบทบาทผู้ดูแลระบบ กรุณากำหนดบทบาทผู้ดูแลระบบให้ผู้ใช้อื่นก่อนที่จะลบออกที่นี่', + + // Comments + 'comment_list' => 'เกิดข้อผิดพลาดขณะดึงความคิดเห็น', + 'cannot_add_comment_to_draft' => 'ไม่สามารถเพิ่มความคิดเห็นในร่างได้', + 'comment_add' => 'เกิดข้อผิดพลาดขณะเพิ่ม/อัปเดตความคิดเห็น', + 'comment_delete' => 'เกิดข้อผิดพลาดขณะลบความคิดเห็น', + 'empty_comment' => 'ไม่สามารถเพิ่มความคิดเห็นที่ว่างเปล่าได้', + + // Error pages + '404_page_not_found' => 'ไม่พบหน้า', + 'sorry_page_not_found' => 'ขออภัย ไม่พบหน้าที่คุณกำลังมองหา', + 'sorry_page_not_found_permission_warning' => 'หากคุณคาดว่าหน้านี้มีอยู่ คุณอาจไม่มีสิทธิ์ดูหน้านี้', + 'image_not_found' => 'ไม่พบรูปภาพ', + 'image_not_found_subtitle' => 'ขออภัย ไม่พบไฟล์รูปภาพที่คุณกำลังมองหา', + 'image_not_found_details' => 'หากคุณคาดว่ารูปภาพนี้มีอยู่ อาจถูกลบไปแล้ว', + 'return_home' => 'กลับไปหน้าแรก', + 'error_occurred' => 'เกิดข้อผิดพลาด', + 'app_down' => ':appName ไม่พร้อมใช้งานในขณะนี้', + 'back_soon' => 'จะกลับมาให้บริการเร็วๆ นี้', + + // Import + 'import_zip_cant_read' => 'ไม่สามารถอ่านไฟล์ ZIP ได้', + 'import_zip_cant_decode_data' => 'ไม่สามารถค้นหาและถอดรหัสเนื้อหา data.json ใน ZIP ได้', + 'import_zip_no_data' => 'ข้อมูลในไฟล์ ZIP ไม่มีเนื้อหาหนังสือ บท หรือหน้าที่คาดไว้', + 'import_zip_data_too_large' => 'เนื้อหา data.json ใน ZIP เกินขนาดอัปโหลดสูงสุดที่กำหนดในแอปพลิเคชัน', + 'import_validation_failed' => 'ตรวจสอบ ZIP นำเข้าล้มเหลวพร้อมข้อผิดพลาด:', + 'import_zip_failed_notification' => 'นำเข้าไฟล์ ZIP ล้มเหลว', + 'import_perms_books' => 'คุณขาดสิทธิ์ที่จำเป็นในการสร้างหนังสือ', + 'import_perms_chapters' => 'คุณขาดสิทธิ์ที่จำเป็นในการสร้างบท', + 'import_perms_pages' => 'คุณขาดสิทธิ์ที่จำเป็นในการสร้างหน้า', + 'import_perms_images' => 'คุณขาดสิทธิ์ที่จำเป็นในการสร้างรูปภาพ', + 'import_perms_attachments' => 'คุณขาดสิทธิ์ที่จำเป็นในการสร้างไฟล์แนบ', + + // API errors + 'api_no_authorization_found' => 'ไม่พบโทเค็นการอนุญาตในคำขอ', + 'api_bad_authorization_format' => 'พบโทเค็นการอนุญาตในคำขอแต่รูปแบบดูเหมือนไม่ถูกต้อง', + 'api_user_token_not_found' => 'ไม่พบ API token ที่ตรงกับโทเค็นการอนุญาตที่ให้มา', + 'api_incorrect_token_secret' => 'รหัสลับที่ให้มาสำหรับ API token ที่ใช้ไม่ถูกต้อง', + 'api_user_no_api_permission' => 'เจ้าของ API token ที่ใช้ไม่มีสิทธิ์เรียกใช้ API', + 'api_user_token_expired' => 'โทเค็นการอนุญาตที่ใช้หมดอายุแล้ว', + 'api_cookie_auth_only_get' => 'อนุญาตเฉพาะคำขอ GET เมื่อใช้ API ด้วยการยืนยันตัวตนแบบ cookie', + + // Settings & Maintenance + 'maintenance_test_email_failure' => 'เกิดข้อผิดพลาดขณะส่งอีเมลทดสอบ:', + + // HTTP errors + 'http_ssr_url_no_match' => 'URL ไม่ตรงกับโฮสต์ SSR ที่อนุญาตที่กำหนดค่าไว้', +]; diff --git a/lang/th/notifications.php b/lang/th/notifications.php new file mode 100644 index 00000000000..661c83fdb25 --- /dev/null +++ b/lang/th/notifications.php @@ -0,0 +1,29 @@ + 'ความคิดเห็นใหม่ในหน้า: :pageName', + 'new_comment_intro' => 'มีผู้ใช้แสดงความคิดเห็นในหน้าใน :appName:', + 'new_page_subject' => 'หน้าใหม่: :pageName', + 'new_page_intro' => 'มีการสร้างหน้าใหม่ใน :appName:', + 'updated_page_subject' => 'แก้ไขหน้า: :pageName', + 'updated_page_intro' => 'มีการแก้ไขหน้าใน :appName:', + 'updated_page_debounce' => 'เพื่อป้องกันการแจ้งเตือนจำนวนมาก คุณจะไม่ได้รับการแจ้งเตือนสำหรับการแก้ไขเพิ่มเติมในหน้านี้โดยผู้แก้ไขคนเดิมในช่วงเวลาหนึ่ง', + 'comment_mention_subject' => 'คุณถูกกล่าวถึงในความคิดเห็นในหน้า: :pageName', + 'comment_mention_intro' => 'คุณถูกกล่าวถึงในความคิดเห็นใน :appName:', + + 'detail_page_name' => 'ชื่อหน้า:', + 'detail_page_path' => 'พาธหน้า:', + 'detail_commenter' => 'ผู้แสดงความคิดเห็น:', + 'detail_comment' => 'ความคิดเห็น:', + 'detail_created_by' => 'สร้างโดย:', + 'detail_updated_by' => 'แก้ไขโดย:', + + 'action_view_comment' => 'ดูความคิดเห็น', + 'action_view_page' => 'ดูหน้า', + + 'footer_reason' => 'คุณได้รับการแจ้งเตือนนี้เพราะ :link ครอบคลุมกิจกรรมประเภทนี้สำหรับรายการนี้', + 'footer_reason_link' => 'การตั้งค่าการแจ้งเตือนของคุณ', +]; diff --git a/lang/th/pagination.php b/lang/th/pagination.php new file mode 100644 index 00000000000..c29e7d0d9ce --- /dev/null +++ b/lang/th/pagination.php @@ -0,0 +1,12 @@ + '« ก่อนหน้า', + 'next' => 'ถัดไป »', + +]; diff --git a/lang/th/passwords.php b/lang/th/passwords.php new file mode 100644 index 00000000000..3429c776394 --- /dev/null +++ b/lang/th/passwords.php @@ -0,0 +1,15 @@ + 'รหัสผ่านต้องมีอย่างน้อยแปดตัวอักษรและต้องตรงกับการยืนยัน', + 'user' => "ไม่พบผู้ใช้ที่ใช้ที่อยู่อีเมลนี้", + 'token' => 'โทเค็นรีเซ็ตรหัสผ่านไม่ถูกต้องสำหรับที่อยู่อีเมลนี้', + 'sent' => 'ส่งลิงก์รีเซ็ตรหัสผ่านไปยังอีเมลของคุณแล้ว!', + 'reset' => 'รีเซ็ตรหัสผ่านของคุณแล้ว!', + +]; diff --git a/lang/th/preferences.php b/lang/th/preferences.php new file mode 100644 index 00000000000..56d57fc2669 --- /dev/null +++ b/lang/th/preferences.php @@ -0,0 +1,52 @@ + 'บัญชีของฉัน', + + 'shortcuts' => 'แป้นพิมพ์ลัด', + 'shortcuts_interface' => 'การตั้งค่าแป้นพิมพ์ลัด UI', + 'shortcuts_toggle_desc' => 'คุณสามารถเปิดหรือปิดแป้นพิมพ์ลัดของระบบที่ใช้สำหรับการนำทางและการดำเนินการได้ที่นี่', + 'shortcuts_customize_desc' => 'คุณสามารถปรับแต่งแป้นพิมพ์ลัดแต่ละรายการด้านล่างได้ เพียงกดคีย์ผสมที่ต้องการหลังจากเลือกช่องป้อนข้อมูลสำหรับแป้นลัดนั้น', + 'shortcuts_toggle_label' => 'เปิดใช้งานแป้นพิมพ์ลัด', + 'shortcuts_section_navigation' => 'การนำทาง', + 'shortcuts_section_actions' => 'การดำเนินการทั่วไป', + 'shortcuts_save' => 'บันทึกแป้นพิมพ์ลัด', + 'shortcuts_overlay_desc' => 'หมายเหตุ: เมื่อเปิดใช้งานแป้นพิมพ์ลัด จะมีแผงช่วยเหลือที่เข้าถึงได้โดยกด "?" ซึ่งจะแสดงแป้นพิมพ์ลัดที่ใช้ได้สำหรับการดำเนินการที่แสดงอยู่บนหน้าจอ', + 'shortcuts_update_success' => 'อัปเดตการตั้งค่าแป้นพิมพ์ลัดแล้ว!', + 'shortcuts_overview_desc' => 'จัดการแป้นพิมพ์ลัดที่ใช้นำทางในส่วนติดต่อผู้ใช้ของระบบ', + + 'notifications' => 'การตั้งค่าการแจ้งเตือน', + 'notifications_desc' => 'ควบคุมการแจ้งเตือนทางอีเมลที่คุณได้รับเมื่อมีกิจกรรมบางอย่างในระบบ', + 'notifications_opt_own_page_changes' => 'แจ้งเตือนเมื่อมีการเปลี่ยนแปลงหน้าที่ฉันเป็นเจ้าของ', + 'notifications_opt_own_page_comments' => 'แจ้งเตือนเมื่อมีความคิดเห็นในหน้าที่ฉันเป็นเจ้าของ', + 'notifications_opt_comment_mentions' => 'แจ้งเตือนเมื่อฉันถูกกล่าวถึงในความคิดเห็น', + 'notifications_opt_comment_replies' => 'แจ้งเตือนเมื่อมีการตอบกลับความคิดเห็นของฉัน', + 'notifications_save' => 'บันทึกการตั้งค่า', + 'notifications_update_success' => 'อัปเดตการตั้งค่าการแจ้งเตือนแล้ว!', + 'notifications_watched' => 'รายการที่ติดตามและไม่สนใจ', + 'notifications_watched_desc' => 'รายการด้านล่างมีการตั้งค่าการติดตามแบบกำหนดเอง หากต้องการอัปเดตการตั้งค่า ให้ดูรายการนั้นแล้วค้นหาตัวเลือกการติดตามในแถบด้านข้าง', + + 'auth' => 'การเข้าถึงและความปลอดภัย', + 'auth_change_password' => 'เปลี่ยนรหัสผ่าน', + 'auth_change_password_desc' => 'เปลี่ยนรหัสผ่านที่ใช้เข้าสู่ระบบ ต้องมีความยาวอย่างน้อย 8 ตัวอักษร', + 'auth_change_password_success' => 'อัปเดตรหัสผ่านแล้ว!', + + 'profile' => 'รายละเอียดโปรไฟล์', + 'profile_desc' => 'จัดการรายละเอียดบัญชีที่แสดงตัวตนต่อผู้ใช้อื่น รวมถึงรายละเอียดที่ใช้สำหรับการสื่อสารและการปรับแต่งระบบ', + 'profile_view_public' => 'ดูโปรไฟล์สาธารณะ', + 'profile_name_desc' => 'กำหนดชื่อที่แสดงซึ่งจะมองเห็นได้โดยผู้ใช้อื่นในระบบผ่านกิจกรรมที่คุณดำเนินการและเนื้อหาที่คุณเป็นเจ้าของ', + 'profile_email_desc' => 'อีเมลนี้จะใช้สำหรับการแจ้งเตือนและการเข้าถึงระบบขึ้นอยู่กับการยืนยันตัวตนที่ใช้งานอยู่', + 'profile_email_no_permission' => 'ขออภัย คุณไม่มีสิทธิ์เปลี่ยนที่อยู่อีเมล หากต้องการเปลี่ยน กรุณาติดต่อผู้ดูแลระบบ', + 'profile_avatar_desc' => 'เลือกรูปภาพที่จะใช้แสดงตัวตนต่อผู้อื่นในระบบ รูปภาพควรเป็นรูปสี่เหลี่ยมจัตุรัสขนาดประมาณ 256px', + 'profile_admin_options' => 'ตัวเลือกสำหรับผู้ดูแลระบบ', + 'profile_admin_options_desc' => 'ตัวเลือกระดับผู้ดูแลระบบเพิ่มเติม เช่น การจัดการการกำหนดบทบาท สามารถพบได้ในพื้นที่ "การตั้งค่า > ผู้ใช้" ของแอปพลิเคชัน', + + 'delete_account' => 'ลบบัญชี', + 'delete_my_account' => 'ลบบัญชีของฉัน', + 'delete_my_account_desc' => 'การดำเนินการนี้จะลบบัญชีผู้ใช้ของคุณออกจากระบบทั้งหมด คุณจะไม่สามารถกู้คืนบัญชีหรือยกเลิกการดำเนินการนี้ได้ เนื้อหาที่คุณสร้าง เช่น หน้าที่สร้างและรูปภาพที่อัปโหลด จะยังคงอยู่', + 'delete_my_account_warning' => 'คุณแน่ใจหรือไม่ว่าต้องการลบบัญชีของคุณ?', +]; diff --git a/lang/th/settings.php b/lang/th/settings.php new file mode 100644 index 00000000000..1b90d2a7196 --- /dev/null +++ b/lang/th/settings.php @@ -0,0 +1,375 @@ + 'การตั้งค่า', + 'settings_save' => 'บันทึกการตั้งค่า', + 'system_version' => 'เวอร์ชันระบบ', + 'categories' => 'หมวดหมู่', + + // App Settings + 'app_customization' => 'การปรับแต่ง', + 'app_features_security' => 'คุณสมบัติและความปลอดภัย', + 'app_name' => 'ชื่อแอปพลิเคชัน', + 'app_name_desc' => 'ชื่อนี้จะแสดงในส่วนหัวและอีเมลที่ส่งโดยระบบ', + 'app_name_header' => 'แสดงชื่อในส่วนหัว', + 'app_public_access' => 'การเข้าถึงสาธารณะ', + 'app_public_access_desc' => 'การเปิดใช้งานตัวเลือกนี้จะอนุญาตให้ผู้เยี่ยมชมที่ไม่ได้เข้าสู่ระบบสามารถเข้าถึงเนื้อหาใน BookStack ของคุณได้', + 'app_public_access_desc_guest' => 'การเข้าถึงของผู้เยี่ยมชมสาธารณะสามารถควบคุมได้ผ่านผู้ใช้ "Guest"', + 'app_public_access_toggle' => 'อนุญาตการเข้าถึงสาธารณะ', + 'app_public_viewing' => 'อนุญาตให้ดูแบบสาธารณะ?', + 'app_secure_images' => 'การอัปโหลดรูปภาพแบบความปลอดภัยสูง', + 'app_secure_images_toggle' => 'เปิดใช้งานการอัปโหลดรูปภาพแบบความปลอดภัยสูง', + 'app_secure_images_desc' => 'เพื่อประสิทธิภาพ รูปภาพทั้งหมดเป็นสาธารณะ ตัวเลือกนี้จะเพิ่มสตริงสุ่มที่คาดเดาได้ยากหน้า URL รูปภาพ ตรวจสอบให้แน่ใจว่าไม่ได้เปิดใช้งานการแสดงรายการไดเรกทอรีเพื่อป้องกันการเข้าถึงที่ง่าย', + 'app_default_editor' => 'ตัวแก้ไขหน้าเริ่มต้น', + 'app_default_editor_desc' => 'เลือกตัวแก้ไขที่จะใช้โดยค่าเริ่มต้นเมื่อแก้ไขหน้าใหม่ ซึ่งสามารถแทนที่ได้ในระดับหน้าเมื่อมีสิทธิ์อนุญาต', + 'app_custom_html' => 'เนื้อหา HTML Head แบบกำหนดเอง', + 'app_custom_html_desc' => 'เนื้อหาที่เพิ่มที่นี่จะถูกแทรกที่ด้านล่างของส่วน ของทุกหน้า มีประโยชน์สำหรับการแทนที่สไตล์หรือเพิ่มโค้ด Analytics', + 'app_custom_html_disabled_notice' => 'เนื้อหา HTML head แบบกำหนดเองถูกปิดใช้งานในหน้าการตั้งค่านี้เพื่อให้สามารถเปลี่ยนแปลงที่ทำให้ใช้งานไม่ได้กลับคืนได้', + 'app_logo' => 'โลโก้แอปพลิเคชัน', + 'app_logo_desc' => 'ใช้ในแถบส่วนหัวของแอปพลิเคชัน รูปภาพควรมีความสูง 86px รูปภาพขนาดใหญ่จะถูกย่อขนาด', + 'app_icon' => 'ไอคอนแอปพลิเคชัน', + 'app_icon_desc' => 'ไอคอนนี้ใช้สำหรับแท็บเบราว์เซอร์และไอคอนทางลัด ควรเป็นรูปภาพ PNG สี่เหลี่ยมขนาด 256px', + 'app_homepage' => 'หน้าแรกของแอปพลิเคชัน', + 'app_homepage_desc' => 'เลือกมุมมองที่จะแสดงบนหน้าแรกแทนมุมมองเริ่มต้น สิทธิ์หน้าจะถูกละเว้นสำหรับหน้าที่เลือก', + 'app_homepage_select' => 'เลือกหน้า', + 'app_footer_links' => 'ลิงก์ส่วนท้าย', + 'app_footer_links_desc' => 'เพิ่มลิงก์เพื่อแสดงในส่วนท้ายของเว็บไซต์ จะแสดงที่ด้านล่างของส่วนใหญ่ของหน้า รวมถึงหน้าที่ไม่ต้องเข้าสู่ระบบ คุณสามารถใช้ป้ายชื่อ "trans::" เพื่อใช้การแปลที่ระบบกำหนดไว้ เช่น "trans::common.privacy_policy" จะให้ข้อความ "นโยบายความเป็นส่วนตัว"', + 'app_footer_links_label' => 'ป้ายชื่อลิงก์', + 'app_footer_links_url' => 'URL ลิงก์', + 'app_footer_links_add' => 'เพิ่มลิงก์ส่วนท้าย', + 'app_disable_comments' => 'ปิดใช้งานความคิดเห็น', + 'app_disable_comments_toggle' => 'ปิดใช้งานความคิดเห็น', + 'app_disable_comments_desc' => 'ปิดใช้งานความคิดเห็นในทุกหน้าในแอปพลิเคชัน
    ความคิดเห็นที่มีอยู่จะไม่ถูกแสดง', + + // Color settings + 'color_scheme' => 'โครงสีของแอปพลิเคชัน', + 'color_scheme_desc' => 'ตั้งค่าสีที่จะใช้ในส่วนติดต่อผู้ใช้ของแอปพลิเคชัน สามารถกำหนดค่าสีแยกกันสำหรับโหมดมืดและโหมดสว่างเพื่อให้เหมาะกับธีมและรับประกันการอ่านง่าย', + 'ui_colors_desc' => 'ตั้งค่าสีหลักและสีลิงก์เริ่มต้นของแอปพลิเคชัน สีหลักใช้สำหรับแบนเนอร์ส่วนหัว ปุ่ม และการตกแต่งส่วนติดต่อ สีลิงก์เริ่มต้นใช้สำหรับลิงก์และการดำเนินการที่เป็นข้อความ', + 'app_color' => 'สีหลัก', + 'link_color' => 'สีลิงก์เริ่มต้น', + 'content_colors_desc' => 'ตั้งค่าสีสำหรับองค์ประกอบทั้งหมดในลำดับชั้นการจัดระเบียบหน้า แนะนำให้เลือกสีที่มีความสว่างใกล้เคียงกับสีเริ่มต้นเพื่อการอ่านง่าย', + 'bookshelf_color' => 'สีชั้นวาง', + 'book_color' => 'สีหนังสือ', + 'chapter_color' => 'สีบท', + 'page_color' => 'สีหน้า', + 'page_draft_color' => 'สีร่างหน้า', + + // Registration Settings + 'reg_settings' => 'การลงทะเบียน', + 'reg_enable' => 'เปิดใช้งานการลงทะเบียน', + 'reg_enable_toggle' => 'เปิดใช้งานการลงทะเบียน', + 'reg_enable_desc' => 'เมื่อเปิดใช้งานการลงทะเบียน ผู้ใช้จะสามารถสมัครเป็นผู้ใช้แอปพลิเคชันได้ด้วยตนเอง เมื่อลงทะเบียนแล้วจะได้รับบทบาทผู้ใช้เริ่มต้นหนึ่งบทบาท', + 'reg_default_role' => 'บทบาทผู้ใช้เริ่มต้นหลังการลงทะเบียน', + 'reg_enable_external_warning' => 'ตัวเลือกด้านบนจะถูกละเว้นเมื่อการยืนยันตัวตน LDAP หรือ SAML ภายนอกเปิดใช้งานอยู่ บัญชีผู้ใช้สำหรับสมาชิกที่ไม่มีอยู่จะถูกสร้างอัตโนมัติหากการยืนยันตัวตนกับระบบภายนอกที่ใช้งานอยู่สำเร็จ', + 'reg_email_confirmation' => 'การยืนยันอีเมล', + 'reg_email_confirmation_toggle' => 'ต้องยืนยันอีเมล', + 'reg_confirm_email_desc' => 'หากใช้การจำกัดโดเมน จะต้องยืนยันอีเมลและตัวเลือกนี้จะถูกละเว้น', + 'reg_confirm_restrict_domain' => 'การจำกัดโดเมน', + 'reg_confirm_restrict_domain_desc' => 'กรอกรายการโดเมนอีเมลที่คั่นด้วยเครื่องหมายจุลภาคที่ต้องการจำกัดการลงทะเบียน ผู้ใช้จะได้รับอีเมลเพื่อยืนยันที่อยู่ก่อนที่จะสามารถโต้ตอบกับแอปพลิเคชัน
    ผู้ใช้จะสามารถเปลี่ยนที่อยู่อีเมลได้หลังจากลงทะเบียนสำเร็จ', + 'reg_confirm_restrict_domain_placeholder' => 'ไม่มีการจำกัด', + + // Sorting Settings + 'sorting' => 'รายการและการจัดเรียง', + 'sorting_book_default' => 'กฎการจัดเรียงหนังสือเริ่มต้น', + 'sorting_book_default_desc' => 'เลือกกฎการจัดเรียงเริ่มต้นที่จะใช้กับหนังสือใหม่ ไม่มีผลกับหนังสือที่มีอยู่แล้ว และสามารถแทนที่ได้ต่อหนังสือ', + 'sorting_rules' => 'กฎการจัดเรียง', + 'sorting_rules_desc' => 'นี่คือการดำเนินการจัดเรียงที่กำหนดไว้ล่วงหน้าซึ่งสามารถนำไปใช้กับเนื้อหาในระบบ', + 'sort_rule_assigned_to_x_books' => 'กำหนดให้ :count หนังสือ', + 'sort_rule_create' => 'สร้างกฎการจัดเรียง', + 'sort_rule_edit' => 'แก้ไขกฎการจัดเรียง', + 'sort_rule_delete' => 'ลบกฎการจัดเรียง', + 'sort_rule_delete_desc' => 'ลบกฎการจัดเรียงนี้ออกจากระบบ หนังสือที่ใช้กฎนี้จะกลับไปใช้การจัดเรียงแบบกำหนดเอง', + 'sort_rule_delete_warn_books' => 'กฎการจัดเรียงนี้ใช้งานอยู่กับ :count เล่ม คุณแน่ใจหรือไม่ว่าต้องการลบ?', + 'sort_rule_delete_warn_default' => 'กฎการจัดเรียงนี้ใช้งานอยู่เป็นค่าเริ่มต้นสำหรับหนังสือ คุณแน่ใจหรือไม่ว่าต้องการลบ?', + 'sort_rule_details' => 'รายละเอียดกฎการจัดเรียง', + 'sort_rule_details_desc' => 'ตั้งชื่อสำหรับกฎการจัดเรียงนี้ ซึ่งจะปรากฏในรายการเมื่อผู้ใช้เลือกการจัดเรียง', + 'sort_rule_operations' => 'การดำเนินการจัดเรียง', + 'sort_rule_operations_desc' => 'กำหนดการดำเนินการจัดเรียงโดยย้ายจากรายการที่มีให้ เมื่อใช้งาน การดำเนินการจะถูกนำไปใช้ตามลำดับจากบนลงล่าง การเปลี่ยนแปลงจะถูกนำไปใช้กับหนังสือที่กำหนดทั้งหมดเมื่อบันทึก', + 'sort_rule_available_operations' => 'การดำเนินการที่มีให้', + 'sort_rule_available_operations_empty' => 'ไม่มีการดำเนินการเหลือ', + 'sort_rule_configured_operations' => 'การดำเนินการที่กำหนดค่า', + 'sort_rule_configured_operations_empty' => 'ลาก/เพิ่มการดำเนินการจากรายการ "การดำเนินการที่มีให้"', + 'sort_rule_op_asc' => '(น้อยไปมาก)', + 'sort_rule_op_desc' => '(มากไปน้อย)', + 'sort_rule_op_name' => 'ชื่อ - ตามตัวอักษร', + 'sort_rule_op_name_numeric' => 'ชื่อ - ตามตัวเลข', + 'sort_rule_op_created_date' => 'วันที่สร้าง', + 'sort_rule_op_updated_date' => 'วันที่แก้ไข', + 'sort_rule_op_chapters_first' => 'บทอยู่ก่อน', + 'sort_rule_op_chapters_last' => 'บทอยู่หลัง', + 'sorting_page_limits' => 'ขีดจำกัดการแสดงผลต่อหน้า', + 'sorting_page_limits_desc' => 'ตั้งค่าจำนวนรายการที่จะแสดงต่อหน้าในรายการต่างๆ ในระบบ โดยทั่วไปจำนวนน้อยจะมีประสิทธิภาพดีกว่า ขณะที่จำนวนมากจะลดความจำเป็นในการคลิกผ่านหลายหน้า แนะนำให้ใช้ทวีคูณของ 6', + + // Maintenance settings + 'maint' => 'การบำรุงรักษา', + 'maint_image_cleanup' => 'ล้างรูปภาพ', + 'maint_image_cleanup_desc' => 'สแกนเนื้อหาหน้าและการแก้ไขเพื่อตรวจสอบว่ารูปภาพและภาพวาดใดกำลังใช้งานอยู่และรูปภาพใดซ้ำซ้อน ตรวจสอบให้แน่ใจว่าสร้างข้อมูลสำรองฐานข้อมูลและรูปภาพฉบับสมบูรณ์ก่อนรัน', + 'maint_delete_images_only_in_revisions' => 'ลบรูปภาพที่มีเฉพาะในการแก้ไขหน้าเก่าด้วย', + 'maint_image_cleanup_run' => 'รันการล้างข้อมูล', + 'maint_image_cleanup_warning' => 'พบรูปภาพที่อาจไม่ได้ใช้งาน :count รายการ คุณแน่ใจหรือไม่ว่าต้องการลบรูปภาพเหล่านี้?', + 'maint_image_cleanup_success' => 'พบและลบรูปภาพที่อาจไม่ได้ใช้งาน :count รายการ!', + 'maint_image_cleanup_nothing_found' => 'ไม่พบรูปภาพที่ไม่ได้ใช้งาน ไม่มีการลบ!', + 'maint_send_test_email' => 'ส่งอีเมลทดสอบ', + 'maint_send_test_email_desc' => 'ส่งอีเมลทดสอบไปยังที่อยู่อีเมลที่ระบุในโปรไฟล์ของคุณ', + 'maint_send_test_email_run' => 'ส่งอีเมลทดสอบ', + 'maint_send_test_email_success' => 'ส่งอีเมลไปยัง :address แล้ว', + 'maint_send_test_email_mail_subject' => 'อีเมลทดสอบ', + 'maint_send_test_email_mail_greeting' => 'การส่งอีเมลดูเหมือนจะทำงานได้!', + 'maint_send_test_email_mail_text' => 'ยินดีด้วย! เนื่องจากคุณได้รับการแจ้งเตือนทางอีเมลนี้ การตั้งค่าอีเมลของคุณดูเหมือนจะได้รับการกำหนดค่าอย่างถูกต้อง', + 'maint_recycle_bin_desc' => 'ชั้นวาง หนังสือ บท และหน้าที่ถูกลบจะถูกส่งไปยังถังรีไซเคิลเพื่อให้สามารถกู้คืนหรือลบถาวรได้ รายการเก่าในถังรีไซเคิลอาจถูกลบโดยอัตโนมัติหลังจากสักพักขึ้นอยู่กับการกำหนดค่าระบบ', + 'maint_recycle_bin_open' => 'เปิดถังรีไซเคิล', + 'maint_regen_references' => 'สร้างการอ้างอิงใหม่', + 'maint_regen_references_desc' => 'การดำเนินการนี้จะสร้างดัชนีการอ้างอิงข้ามรายการในฐานข้อมูลใหม่ โดยปกติจะจัดการโดยอัตโนมัติ แต่การดำเนินการนี้มีประโยชน์สำหรับการสร้างดัชนีเนื้อหาเก่าหรือเนื้อหาที่เพิ่มผ่านวิธีที่ไม่เป็นทางการ', + 'maint_regen_references_success' => 'สร้างดัชนีการอ้างอิงใหม่แล้ว!', + 'maint_timeout_command_note' => 'หมายเหตุ: การดำเนินการนี้อาจใช้เวลานาน ซึ่งอาจทำให้เกิดปัญหา timeout ในบางสภาพแวดล้อมเว็บ ทางเลือกหนึ่งคือดำเนินการผ่านคำสั่ง terminal', + + // Recycle Bin + 'recycle_bin' => 'ถังรีไซเคิล', + 'recycle_bin_desc' => 'คุณสามารถกู้คืนรายการที่ถูกลบหรือเลือกลบออกจากระบบถาวรได้ที่นี่ รายการนี้ไม่ได้กรองต่างจากรายการกิจกรรมที่คล้ายกันในระบบซึ่งมีการกรองสิทธิ์', + 'recycle_bin_deleted_item' => 'รายการที่ถูกลบ', + 'recycle_bin_deleted_parent' => 'รายการแม่', + 'recycle_bin_deleted_by' => 'ลบโดย', + 'recycle_bin_deleted_at' => 'เวลาที่ลบ', + 'recycle_bin_permanently_delete' => 'ลบถาวร', + 'recycle_bin_restore' => 'กู้คืน', + 'recycle_bin_contents_empty' => 'ถังรีไซเคิลว่างเปล่า', + 'recycle_bin_empty' => 'ล้างถังรีไซเคิล', + 'recycle_bin_empty_confirm' => 'การดำเนินการนี้จะทำลายรายการทั้งหมดในถังรีไซเคิลอย่างถาวร รวมถึงเนื้อหาที่อยู่ในแต่ละรายการ คุณแน่ใจหรือไม่ว่าต้องการล้างถังรีไซเคิล?', + 'recycle_bin_destroy_confirm' => 'การดำเนินการนี้จะลบรายการนี้ออกจากระบบอย่างถาวร พร้อมกับองค์ประกอบลูกที่แสดงด้านล่าง และคุณจะไม่สามารถกู้คืนเนื้อหานี้ได้ คุณแน่ใจหรือไม่?', + 'recycle_bin_destroy_list' => 'รายการที่จะถูกทำลาย', + 'recycle_bin_restore_list' => 'รายการที่จะถูกกู้คืน', + 'recycle_bin_restore_confirm' => 'การดำเนินการนี้จะกู้คืนรายการที่ถูกลบ รวมถึงองค์ประกอบลูก ไปยังตำแหน่งเดิม หากตำแหน่งเดิมถูกลบไปแล้วและอยู่ในถังรีไซเคิล จะต้องกู้คืนรายการแม่ด้วย', + 'recycle_bin_restore_deleted_parent' => 'รายการแม่ของรายการนี้ถูกลบเช่นกัน จะยังคงถูกลบจนกว่าจะกู้คืนรายการแม่', + 'recycle_bin_restore_parent' => 'กู้คืนรายการแม่', + 'recycle_bin_destroy_notification' => 'ลบรายการทั้งหมด :count รายการจากถังรีไซเคิล', + 'recycle_bin_restore_notification' => 'กู้คืนรายการทั้งหมด :count รายการจากถังรีไซเคิล', + + // Audit Log + 'audit' => 'บันทึกการตรวจสอบ', + 'audit_desc' => 'บันทึกการตรวจสอบนี้แสดงรายการกิจกรรมที่ติดตามในระบบ รายการนี้ไม่ได้กรองต่างจากรายการกิจกรรมที่คล้ายกันในระบบซึ่งมีการกรองสิทธิ์', + 'audit_event_filter' => 'ตัวกรองกิจกรรม', + 'audit_event_filter_no_filter' => 'ไม่มีตัวกรอง', + 'audit_deleted_item' => 'รายการที่ถูกลบ', + 'audit_deleted_item_name' => 'ชื่อ: :name', + 'audit_table_user' => 'ผู้ใช้', + 'audit_table_event' => 'กิจกรรม', + 'audit_table_related' => 'รายการหรือรายละเอียดที่เกี่ยวข้อง', + 'audit_table_ip' => 'IP Address', + 'audit_table_date' => 'วันที่กิจกรรม', + 'audit_date_from' => 'ช่วงวันที่ตั้งแต่', + 'audit_date_to' => 'ช่วงวันที่ถึง', + + // Role Settings + 'roles' => 'บทบาท', + 'role_user_roles' => 'บทบาทผู้ใช้', + 'roles_index_desc' => 'บทบาทใช้สำหรับจัดกลุ่มผู้ใช้และให้สิทธิ์ระบบแก่สมาชิก เมื่อผู้ใช้เป็นสมาชิกของหลายบทบาท สิทธิ์ที่ได้รับจะสะสมกัน และผู้ใช้จะได้รับความสามารถทั้งหมด', + 'roles_x_users_assigned' => 'กำหนด :count ผู้ใช้', + 'roles_x_permissions_provided' => ':count สิทธิ์', + 'roles_assigned_users' => 'ผู้ใช้ที่กำหนด', + 'roles_permissions_provided' => 'สิทธิ์ที่ให้', + 'role_create' => 'สร้างบทบาทใหม่', + 'role_delete' => 'ลบบทบาท', + 'role_delete_confirm' => 'การดำเนินการนี้จะลบบทบาทชื่อ \':roleName\'', + 'role_delete_users_assigned' => 'บทบาทนี้มีผู้ใช้ :userCount คนที่กำหนดไว้ หากต้องการย้ายผู้ใช้จากบทบาทนี้ให้เลือกบทบาทใหม่ด้านล่าง', + 'role_delete_no_migration' => "ไม่ต้องย้ายผู้ใช้", + 'role_delete_sure' => 'คุณแน่ใจหรือไม่ว่าต้องการลบบทบาทนี้?', + 'role_edit' => 'แก้ไขบทบาท', + 'role_details' => 'รายละเอียดบทบาท', + 'role_name' => 'ชื่อบทบาท', + 'role_desc' => 'คำอธิบายสั้นของบทบาท', + 'role_mfa_enforced' => 'ต้องใช้การยืนยันตัวตนแบบหลายขั้นตอน', + 'role_external_auth_id' => 'ID การยืนยันตัวตนภายนอก', + 'role_system' => 'สิทธิ์ระบบ', + 'role_manage_users' => 'จัดการผู้ใช้', + 'role_manage_roles' => 'จัดการบทบาทและสิทธิ์บทบาท', + 'role_manage_entity_permissions' => 'จัดการสิทธิ์หนังสือ บท และหน้าทั้งหมด', + 'role_manage_own_entity_permissions' => 'จัดการสิทธิ์บนหนังสือ บท และหน้าของตัวเอง', + 'role_manage_page_templates' => 'จัดการแม่แบบหน้า', + 'role_access_api' => 'เข้าถึง API ระบบ', + 'role_manage_settings' => 'จัดการการตั้งค่าแอปพลิเคชัน', + 'role_export_content' => 'ส่งออกเนื้อหา', + 'role_import_content' => 'นำเข้าเนื้อหา', + 'role_editor_change' => 'เปลี่ยนตัวแก้ไขหน้า', + 'role_notifications' => 'รับและจัดการการแจ้งเตือน', + 'role_permission_note_users_and_roles' => 'สิทธิ์เหล่านี้จะให้การมองเห็นและการค้นหาผู้ใช้และบทบาทในระบบด้วยในทางเทคนิค', + 'role_asset' => 'สิทธิ์ Asset', + 'roles_system_warning' => 'โปรดทราบว่าการเข้าถึงสิทธิ์ใดๆ ในสามสิทธิ์ข้างต้นอาจอนุญาตให้ผู้ใช้เปลี่ยนแปลงสิทธิ์ของตัวเองหรือของผู้อื่นในระบบ กรุณากำหนดบทบาทที่มีสิทธิ์เหล่านี้เฉพาะกับผู้ใช้ที่เชื่อถือได้เท่านั้น', + 'role_asset_desc' => 'สิทธิ์เหล่านี้ควบคุมการเข้าถึงเริ่มต้นสำหรับ asset ในระบบ สิทธิ์บนหนังสือ บท และหน้าจะแทนที่สิทธิ์เหล่านี้', + 'role_asset_admins' => 'ผู้ดูแลระบบได้รับสิทธิ์เข้าถึงเนื้อหาทั้งหมดโดยอัตโนมัติ แต่ตัวเลือกเหล่านี้อาจแสดงหรือซ่อนตัวเลือก UI', + 'role_asset_image_view_note' => 'เกี่ยวข้องกับการมองเห็นภายในตัวจัดการรูปภาพ การเข้าถึงไฟล์รูปภาพที่อัปโหลดจริงจะขึ้นอยู่กับตัวเลือกการจัดเก็บรูปภาพของระบบ', + 'role_asset_users_note' => 'สิทธิ์เหล่านี้จะให้การมองเห็นและการค้นหาผู้ใช้ในระบบด้วยในทางเทคนิค', + 'role_all' => 'ทั้งหมด', + 'role_own' => 'ของตัวเอง', + 'role_controlled_by_asset' => 'ควบคุมโดย asset ที่อัปโหลดไปยัง', + 'role_controlled_by_page_delete' => 'ควบคุมโดยสิทธิ์ลบหน้า', + 'role_save' => 'บันทึกบทบาท', + 'role_users' => 'ผู้ใช้ในบทบาทนี้', + 'role_users_none' => 'ยังไม่มีผู้ใช้ที่กำหนดให้บทบาทนี้', + + // Users + 'users' => 'ผู้ใช้', + 'users_index_desc' => 'สร้างและจัดการบัญชีผู้ใช้แต่ละรายในระบบ บัญชีผู้ใช้ใช้สำหรับเข้าสู่ระบบและระบุที่มาของเนื้อหาและกิจกรรม สิทธิ์การเข้าถึงหลักอิงตามบทบาท แต่ความเป็นเจ้าของเนื้อหาและปัจจัยอื่นๆ อาจส่งผลต่อสิทธิ์และการเข้าถึงด้วย', + 'user_profile' => 'โปรไฟล์ผู้ใช้', + 'users_add_new' => 'เพิ่มผู้ใช้ใหม่', + 'users_search' => 'ค้นหาผู้ใช้', + 'users_latest_activity' => 'กิจกรรมล่าสุด', + 'users_details' => 'รายละเอียดผู้ใช้', + 'users_details_desc' => 'ตั้งชื่อที่แสดงและที่อยู่อีเมลสำหรับผู้ใช้นี้ ที่อยู่อีเมลจะใช้สำหรับเข้าสู่ระบบแอปพลิเคชัน', + 'users_details_desc_no_email' => 'ตั้งชื่อที่แสดงสำหรับผู้ใช้นี้เพื่อให้ผู้อื่นรู้จัก', + 'users_role' => 'บทบาทผู้ใช้', + 'users_role_desc' => 'เลือกบทบาทที่จะกำหนดให้ผู้ใช้นี้ หากผู้ใช้ถูกกำหนดให้หลายบทบาท สิทธิ์จากบทบาทเหล่านั้นจะสะสมกัน', + 'users_password' => 'รหัสผ่านผู้ใช้', + 'users_password_desc' => 'ตั้งรหัสผ่านสำหรับเข้าสู่ระบบ ต้องมีความยาวอย่างน้อย 8 ตัวอักษร', + 'users_send_invite_text' => 'คุณสามารถเลือกส่งอีเมลเชิญให้ผู้ใช้นี้เพื่อให้ตั้งรหัสผ่านของตัวเอง หรือตั้งรหัสผ่านให้เองก็ได้', + 'users_send_invite_option' => 'ส่งอีเมลเชิญผู้ใช้', + 'users_external_auth_id' => 'ID การยืนยันตัวตนภายนอก', + 'users_external_auth_id_desc' => 'เมื่อใช้ระบบยืนยันตัวตนภายนอก (เช่น SAML2, OIDC หรือ LDAP) นี่คือ ID ที่เชื่อมผู้ใช้ BookStack นี้กับบัญชีระบบยืนยันตัวตน สามารถละเว้นช่องนี้หากใช้การยืนยันตัวตนแบบอีเมลเริ่มต้น', + 'users_password_warning' => 'กรอกด้านล่างเฉพาะเมื่อต้องการเปลี่ยนรหัสผ่านสำหรับผู้ใช้นี้', + 'users_system_public' => 'ผู้ใช้นี้แทนผู้เยี่ยมชมที่เข้าถึง instance ของคุณ ไม่สามารถใช้เข้าสู่ระบบได้ แต่จะถูกกำหนดโดยอัตโนมัติ', + 'users_delete' => 'ลบผู้ใช้', + 'users_delete_named' => 'ลบผู้ใช้ :userName', + 'users_delete_warning' => 'การดำเนินการนี้จะลบผู้ใช้ชื่อ \':userName\' ออกจากระบบทั้งหมด', + 'users_delete_confirm' => 'คุณแน่ใจหรือไม่ว่าต้องการลบผู้ใช้นี้?', + 'users_migrate_ownership' => 'ย้ายความเป็นเจ้าของ', + 'users_migrate_ownership_desc' => 'เลือกผู้ใช้ที่นี่หากต้องการให้ผู้ใช้อื่นเป็นเจ้าของรายการทั้งหมดที่เป็นของผู้ใช้นี้ในปัจจุบัน', + 'users_none_selected' => 'ไม่มีผู้ใช้ที่เลือก', + 'users_edit' => 'แก้ไขผู้ใช้', + 'users_edit_profile' => 'แก้ไขโปรไฟล์', + 'users_avatar' => 'รูปโปรไฟล์ผู้ใช้', + 'users_avatar_desc' => 'เลือกรูปภาพเพื่อแสดงตัวผู้ใช้นี้ ควรเป็นรูปสี่เหลี่ยมขนาดประมาณ 256px', + 'users_preferred_language' => 'ภาษาที่ต้องการ', + 'users_preferred_language_desc' => 'ตัวเลือกนี้จะเปลี่ยนภาษาที่ใช้สำหรับส่วนติดต่อผู้ใช้ของแอปพลิเคชัน ไม่มีผลต่อเนื้อหาที่ผู้ใช้สร้าง', + 'users_social_accounts' => 'บัญชี Social', + 'users_social_accounts_desc' => 'ดูสถานะของบัญชี Social ที่เชื่อมต่อสำหรับผู้ใช้นี้ บัญชี Social สามารถใช้นอกเหนือจากระบบยืนยันตัวตนหลักสำหรับการเข้าถึงระบบ', + 'users_social_accounts_info' => 'คุณสามารถเชื่อมต่อบัญชีอื่นที่นี่เพื่อเข้าสู่ระบบที่เร็วและง่ายขึ้น การตัดการเชื่อมต่อบัญชีที่นี่ไม่ได้เพิกถอนสิทธิ์ที่ให้ไว้ก่อนหน้า กรุณาเพิกถอนสิทธิ์จากการตั้งค่าโปรไฟล์บนบัญชี Social ที่เชื่อมต่อ', + 'users_social_connect' => 'เชื่อมต่อบัญชี', + 'users_social_disconnect' => 'ตัดการเชื่อมต่อบัญชี', + 'users_social_status_connected' => 'เชื่อมต่อแล้ว', + 'users_social_status_disconnected' => 'ตัดการเชื่อมต่อแล้ว', + 'users_social_connected' => 'เชื่อมต่อบัญชี :socialAccount กับโปรไฟล์ของคุณสำเร็จแล้ว', + 'users_social_disconnected' => 'ตัดการเชื่อมต่อบัญชี :socialAccount จากโปรไฟล์ของคุณสำเร็จแล้ว', + 'users_api_tokens' => 'API Tokens', + 'users_api_tokens_desc' => 'สร้างและจัดการโทเค็นการเข้าถึงที่ใช้ยืนยันตัวตนกับ BookStack REST API สิทธิ์สำหรับ API จัดการผ่านผู้ใช้ที่โทเค็นนั้นสังกัด', + 'users_api_tokens_none' => 'ยังไม่มี API token ที่สร้างสำหรับผู้ใช้นี้', + 'users_api_tokens_create' => 'สร้าง Token', + 'users_api_tokens_expires' => 'หมดอายุ', + 'users_api_tokens_docs' => 'เอกสาร API', + 'users_mfa' => 'การยืนยันตัวตนแบบหลายขั้นตอน', + 'users_mfa_desc' => 'ตั้งค่าการยืนยันตัวตนแบบหลายขั้นตอนเป็นชั้นความปลอดภัยเพิ่มเติมสำหรับบัญชีผู้ใช้ของคุณ', + 'users_mfa_x_methods' => 'กำหนดค่า :count วิธี', + 'users_mfa_configure' => 'กำหนดค่าวิธี', + + // API Tokens + 'user_api_token_create' => 'สร้าง API Token', + 'user_api_token_name' => 'ชื่อ', + 'user_api_token_name_desc' => 'ตั้งชื่อที่อ่านง่ายสำหรับ token นี้เพื่อเตือนความจำเกี่ยวกับวัตถุประสงค์ในอนาคต', + 'user_api_token_expiry' => 'วันหมดอายุ', + 'user_api_token_expiry_desc' => 'ตั้งวันที่ token นี้หมดอายุ หลังจากวันนี้ คำขอที่ใช้ token นี้จะไม่ทำงานอีกต่อไป การเว้นช่องนี้ว่างจะตั้งวันหมดอายุเป็น 100 ปีในอนาคต', + 'user_api_token_create_secret_message' => 'ทันทีหลังจากสร้าง token นี้ "Token ID" และ "Token Secret" จะถูกสร้างและแสดง Secret จะแสดงเพียงครั้งเดียว ดังนั้นให้คัดลอกค่าไปยังที่ปลอดภัยก่อนดำเนินการต่อ', + 'user_api_token' => 'API Token', + 'user_api_token_id' => 'Token ID', + 'user_api_token_id_desc' => 'นี่คือตัวระบุที่สร้างโดยระบบซึ่งไม่สามารถแก้ไขได้สำหรับ token นี้ ซึ่งจะต้องระบุในคำขอ API', + 'user_api_token_secret' => 'Token Secret', + 'user_api_token_secret_desc' => 'นี่คือ secret ที่สร้างโดยระบบสำหรับ token นี้ซึ่งจะต้องระบุในคำขอ API จะแสดงเพียงครั้งเดียวนี้เท่านั้น ดังนั้นให้คัดลอกค่าไปยังที่ปลอดภัย', + 'user_api_token_created' => 'สร้าง Token เมื่อ :timeAgo', + 'user_api_token_updated' => 'อัปเดต Token เมื่อ :timeAgo', + 'user_api_token_delete' => 'ลบ Token', + 'user_api_token_delete_warning' => 'การดำเนินการนี้จะลบ API token ชื่อ \':tokenName\' ออกจากระบบทั้งหมด', + 'user_api_token_delete_confirm' => 'คุณแน่ใจหรือไม่ว่าต้องการลบ API token นี้?', + + // Webhooks + 'webhooks' => 'Webhooks', + 'webhooks_index_desc' => 'Webhooks เป็นวิธีส่งข้อมูลไปยัง URL ภายนอกเมื่อมีการดำเนินการและกิจกรรมบางอย่างในระบบ ซึ่งช่วยให้สามารถรวมกับแพลตฟอร์มภายนอก เช่น ระบบส่งข้อความหรือการแจ้งเตือน', + 'webhooks_x_trigger_events' => ':count กิจกรรมทริกเกอร์', + 'webhooks_create' => 'สร้าง Webhook ใหม่', + 'webhooks_none_created' => 'ยังไม่มี Webhook ที่สร้าง', + 'webhooks_edit' => 'แก้ไข Webhook', + 'webhooks_save' => 'บันทึก Webhook', + 'webhooks_details' => 'รายละเอียด Webhook', + 'webhooks_details_desc' => 'ระบุชื่อที่เป็นมิตรกับผู้ใช้และ POST endpoint เป็นตำแหน่งสำหรับส่งข้อมูล Webhook', + 'webhooks_events' => 'กิจกรรม Webhook', + 'webhooks_events_desc' => 'เลือกกิจกรรมทั้งหมดที่ควรทริกเกอร์ให้เรียก Webhook นี้', + 'webhooks_events_warning' => 'โปรดทราบว่ากิจกรรมเหล่านี้จะถูกทริกเกอร์สำหรับกิจกรรมที่เลือกทั้งหมด แม้จะมีสิทธิ์แบบกำหนดเองก็ตาม ตรวจสอบให้แน่ใจว่าการใช้ Webhook นี้จะไม่เปิดเผยเนื้อหาที่เป็นความลับ', + 'webhooks_events_all' => 'กิจกรรมทั้งหมดในระบบ', + 'webhooks_name' => 'ชื่อ Webhook', + 'webhooks_timeout' => 'หมดเวลาคำขอ Webhook (วินาที)', + 'webhooks_endpoint' => 'Endpoint ของ Webhook', + 'webhooks_active' => 'Webhook เปิดใช้งาน', + 'webhook_events_table_header' => 'กิจกรรม', + 'webhooks_delete' => 'ลบ Webhook', + 'webhooks_delete_warning' => 'การดำเนินการนี้จะลบ Webhook ชื่อ \':webhookName\' ออกจากระบบทั้งหมด', + 'webhooks_delete_confirm' => 'คุณแน่ใจหรือไม่ว่าต้องการลบ Webhook นี้?', + 'webhooks_format_example' => 'ตัวอย่างรูปแบบ Webhook', + 'webhooks_format_example_desc' => 'ข้อมูล Webhook ถูกส่งเป็นคำขอ POST ไปยัง endpoint ที่กำหนดค่าเป็น JSON ตามรูปแบบด้านล่าง คุณสมบัติ "related_item" และ "url" เป็นทางเลือกและขึ้นอยู่กับประเภทกิจกรรมที่ทริกเกอร์', + 'webhooks_status' => 'สถานะ Webhook', + 'webhooks_last_called' => 'เรียกล่าสุด:', + 'webhooks_last_errored' => 'เกิดข้อผิดพลาดล่าสุด:', + 'webhooks_last_error_message' => 'ข้อความข้อผิดพลาดล่าสุด:', + + // Licensing + 'licenses' => 'สัญญาอนุญาต', + 'licenses_desc' => 'หน้านี้แสดงข้อมูลสัญญาอนุญาตสำหรับ BookStack รวมถึงโปรเจกต์และไลบรารีที่ใช้ใน BookStack โปรเจกต์หลายรายการอาจใช้เฉพาะในบริบทการพัฒนา', + 'licenses_bookstack' => 'สัญญาอนุญาต BookStack', + 'licenses_php' => 'สัญญาอนุญาตไลบรารี PHP', + 'licenses_js' => 'สัญญาอนุญาตไลบรารี JavaScript', + 'licenses_other' => 'สัญญาอนุญาตอื่นๆ', + 'license_details' => 'รายละเอียดสัญญาอนุญาต', + + //! If editing translations files directly please ignore this in all + //! languages apart from en. Content will be auto-copied from en. + //!//////////////////////////////// + 'language_select' => [ + 'en' => 'English', + 'ar' => 'العربية', + 'bg' => 'Bǎlgarski', + 'bs' => 'Bosanski', + 'ca' => 'Català', + 'cs' => 'Česky', + 'cy' => 'Cymraeg', + 'da' => 'Dansk', + 'de' => 'Deutsch (Sie)', + 'de_informal' => 'Deutsch (Du)', + 'el' => 'ελληνικά', + 'es' => 'Español', + 'es_AR' => 'Español Argentina', + 'et' => 'Eesti keel', + 'eu' => 'Euskara', + 'fa' => 'فارسی', + 'fi' => 'Suomi', + 'fr' => 'Français', + 'he' => 'עברית', + 'hr' => 'Hrvatski', + 'hu' => 'Magyar', + 'id' => 'Bahasa Indonesia', + 'it' => 'Italian', + 'ja' => '日本語', + 'ko' => '한국어', + 'lt' => 'Lietuvių Kalba', + 'lv' => 'Latviešu Valoda', + 'nb' => 'Norsk (Bokmål)', + 'ne' => 'नेपाली', + 'nn' => 'Nynorsk', + 'nl' => 'Nederlands', + 'pl' => 'Polski', + 'pt' => 'Português', + 'pt_BR' => 'Português do Brasil', + 'ro' => 'Română', + 'ru' => 'Русский', + 'sk' => 'Slovensky', + 'sl' => 'Slovenščina', + 'sv' => 'Svenska', + 'tr' => 'Türkçe', + 'uk' => 'Українська', + 'uz' => 'O‘zbekcha', + 'vi' => 'Tiếng Việt', + 'zh_CN' => '简体中文', + 'zh_TW' => '繁體中文', + ], + //!//////////////////////////////// +]; diff --git a/lang/th/validation.php b/lang/th/validation.php new file mode 100644 index 00000000000..347eeb36c9e --- /dev/null +++ b/lang/th/validation.php @@ -0,0 +1,123 @@ + 'ต้องยอมรับ :attribute', + 'active_url' => ':attribute ไม่ใช่ URL ที่ถูกต้อง', + 'after' => ':attribute ต้องเป็นวันที่หลังจาก :date', + 'alpha' => ':attribute ต้องมีเฉพาะตัวอักษรเท่านั้น', + 'alpha_dash' => ':attribute ต้องมีเฉพาะตัวอักษร ตัวเลข ขีดกลาง และขีดล่างเท่านั้น', + 'alpha_num' => ':attribute ต้องมีเฉพาะตัวอักษรและตัวเลขเท่านั้น', + 'array' => ':attribute ต้องเป็น array', + 'backup_codes' => 'รหัสที่ให้มาไม่ถูกต้องหรือถูกใช้ไปแล้ว', + 'before' => ':attribute ต้องเป็นวันที่ก่อน :date', + 'between' => [ + 'numeric' => ':attribute ต้องอยู่ระหว่าง :min ถึง :max', + 'file' => ':attribute ต้องมีขนาดระหว่าง :min ถึง :max กิโลไบต์', + 'string' => ':attribute ต้องมีความยาวระหว่าง :min ถึง :max ตัวอักษร', + 'array' => ':attribute ต้องมีระหว่าง :min ถึง :max รายการ', + ], + 'boolean' => 'ฟิลด์ :attribute ต้องเป็น true หรือ false', + 'confirmed' => 'การยืนยัน :attribute ไม่ตรงกัน', + 'date' => ':attribute ไม่ใช่วันที่ที่ถูกต้อง', + 'date_format' => ':attribute ไม่ตรงกับรูปแบบ :format', + 'different' => ':attribute และ :other ต้องแตกต่างกัน', + 'digits' => ':attribute ต้องมี :digits หลัก', + 'digits_between' => ':attribute ต้องมีระหว่าง :min ถึง :max หลัก', + 'email' => ':attribute ต้องเป็นที่อยู่อีเมลที่ถูกต้อง', + 'ends_with' => ':attribute ต้องลงท้ายด้วยหนึ่งในนี้: :values', + 'file' => ':attribute ต้องเป็นไฟล์ที่ถูกต้อง', + 'filled' => 'ฟิลด์ :attribute จำเป็นต้องกรอก', + 'gt' => [ + 'numeric' => ':attribute ต้องมากกว่า :value', + 'file' => ':attribute ต้องมากกว่า :value กิโลไบต์', + 'string' => ':attribute ต้องมีความยาวมากกว่า :value ตัวอักษร', + 'array' => ':attribute ต้องมีมากกว่า :value รายการ', + ], + 'gte' => [ + 'numeric' => ':attribute ต้องมากกว่าหรือเท่ากับ :value', + 'file' => ':attribute ต้องมากกว่าหรือเท่ากับ :value กิโลไบต์', + 'string' => ':attribute ต้องมีความยาวมากกว่าหรือเท่ากับ :value ตัวอักษร', + 'array' => ':attribute ต้องมี :value รายการขึ้นไป', + ], + 'exists' => ':attribute ที่เลือกไม่ถูกต้อง', + 'image' => ':attribute ต้องเป็นรูปภาพ', + 'image_extension' => ':attribute ต้องมีนามสกุลรูปภาพที่ถูกต้องและรองรับ', + 'in' => ':attribute ที่เลือกไม่ถูกต้อง', + 'integer' => ':attribute ต้องเป็นจำนวนเต็ม', + 'ip' => ':attribute ต้องเป็นที่อยู่ IP ที่ถูกต้อง', + 'ipv4' => ':attribute ต้องเป็นที่อยู่ IPv4 ที่ถูกต้อง', + 'ipv6' => ':attribute ต้องเป็นที่อยู่ IPv6 ที่ถูกต้อง', + 'json' => ':attribute ต้องเป็น JSON string ที่ถูกต้อง', + 'lt' => [ + 'numeric' => ':attribute ต้องน้อยกว่า :value', + 'file' => ':attribute ต้องน้อยกว่า :value กิโลไบต์', + 'string' => ':attribute ต้องมีความยาวน้อยกว่า :value ตัวอักษร', + 'array' => ':attribute ต้องมีน้อยกว่า :value รายการ', + ], + 'lte' => [ + 'numeric' => ':attribute ต้องน้อยกว่าหรือเท่ากับ :value', + 'file' => ':attribute ต้องน้อยกว่าหรือเท่ากับ :value กิโลไบต์', + 'string' => ':attribute ต้องมีความยาวน้อยกว่าหรือเท่ากับ :value ตัวอักษร', + 'array' => ':attribute ต้องไม่มีมากกว่า :value รายการ', + ], + 'max' => [ + 'numeric' => ':attribute ต้องไม่มากกว่า :max', + 'file' => ':attribute ต้องไม่มากกว่า :max กิโลไบต์', + 'string' => ':attribute ต้องไม่มากกว่า :max ตัวอักษร', + 'array' => ':attribute ต้องไม่มีมากกว่า :max รายการ', + ], + 'mimes' => ':attribute ต้องเป็นไฟล์ประเภท: :values', + 'min' => [ + 'numeric' => ':attribute ต้องอย่างน้อย :min', + 'file' => ':attribute ต้องอย่างน้อย :min กิโลไบต์', + 'string' => ':attribute ต้องมีความยาวอย่างน้อย :min ตัวอักษร', + 'array' => ':attribute ต้องมีอย่างน้อย :min รายการ', + ], + 'not_in' => ':attribute ที่เลือกไม่ถูกต้อง', + 'not_regex' => 'รูปแบบ :attribute ไม่ถูกต้อง', + 'numeric' => ':attribute ต้องเป็นตัวเลข', + 'regex' => 'รูปแบบ :attribute ไม่ถูกต้อง', + 'required' => 'ฟิลด์ :attribute จำเป็นต้องกรอก', + 'required_if' => 'ฟิลด์ :attribute จำเป็นต้องกรอกเมื่อ :other เป็น :value', + 'required_with' => 'ฟิลด์ :attribute จำเป็นต้องกรอกเมื่อมี :values', + 'required_with_all' => 'ฟิลด์ :attribute จำเป็นต้องกรอกเมื่อมี :values', + 'required_without' => 'ฟิลด์ :attribute จำเป็นต้องกรอกเมื่อไม่มี :values', + 'required_without_all' => 'ฟิลด์ :attribute จำเป็นต้องกรอกเมื่อไม่มีสิ่งใดใน :values', + 'same' => ':attribute และ :other ต้องตรงกัน', + 'safe_url' => 'ลิงก์ที่ให้มาอาจไม่ปลอดภัย', + 'size' => [ + 'numeric' => ':attribute ต้องเป็น :size', + 'file' => ':attribute ต้องมีขนาด :size กิโลไบต์', + 'string' => ':attribute ต้องมีความยาว :size ตัวอักษร', + 'array' => ':attribute ต้องมี :size รายการ', + ], + 'string' => ':attribute ต้องเป็น string', + 'timezone' => ':attribute ต้องเป็นเขตเวลาที่ถูกต้อง', + 'totp' => 'รหัสที่ให้มาไม่ถูกต้องหรือหมดอายุแล้ว', + 'unique' => ':attribute ถูกใช้งานแล้ว', + 'url' => 'รูปแบบ :attribute ไม่ถูกต้อง', + 'uploaded' => 'ไม่สามารถอัปโหลดไฟล์ได้ เซิร์ฟเวอร์อาจไม่รับไฟล์ขนาดนี้', + + 'zip_file' => ':attribute ต้องอ้างอิงถึงไฟล์ภายใน ZIP', + 'zip_file_size' => 'ไฟล์ :attribute ต้องไม่เกิน :size MB', + 'zip_file_mime' => ':attribute ต้องอ้างอิงถึงไฟล์ประเภท :validTypes แต่พบ :foundType', + 'zip_model_expected' => 'คาดหวัง data object แต่พบ ":type"', + 'zip_unique' => ':attribute ต้องไม่ซ้ำกันสำหรับประเภท object ภายใน ZIP', + + // Custom validation lines + 'custom' => [ + 'password-confirm' => [ + 'required_with' => 'จำเป็นต้องยืนยันรหัสผ่าน', + ], + ], + + // Custom validation attributes + 'attributes' => [], +]; diff --git a/lang/tk/entities.php b/lang/tk/entities.php index 74c50be3b2f..5501d2bc229 100644 --- a/lang/tk/entities.php +++ b/lang/tk/entities.php @@ -173,6 +173,7 @@ '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', diff --git a/lang/tk/settings.php b/lang/tk/settings.php index c4d1eb136eb..3937c650f86 100644 --- a/lang/tk/settings.php +++ b/lang/tk/settings.php @@ -207,6 +207,7 @@ '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', diff --git a/lang/tr/entities.php b/lang/tr/entities.php index 39feb7e3917..3d271ecb52f 100644 --- a/lang/tr/entities.php +++ b/lang/tr/entities.php @@ -173,6 +173,7 @@ '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 Kitabını Sırala', 'books_sort_name' => 'İsme Göre Sırala', 'books_sort_created' => 'Oluşturulma Tarihine Göre Sırala', diff --git a/lang/tr/settings.php b/lang/tr/settings.php index a33d3e0ac04..af8d2c6494b 100644 --- a/lang/tr/settings.php +++ b/lang/tr/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Hepsi', 'role_own' => 'Kendine Ait', 'role_controlled_by_asset' => 'Yüklendikleri varlık tarafından kontrol ediliyor', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Rolü Kaydet', 'role_users' => 'Bu roldeki kullanıcılar', 'role_users_none' => 'Bu role henüz bir kullanıcı atanmadı', diff --git a/lang/uk/entities.php b/lang/uk/entities.php index cf79b530cff..6bf7b241ef1 100644 --- a/lang/uk/entities.php +++ b/lang/uk/entities.php @@ -173,6 +173,7 @@ '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' => 'Сортувати за датою створення', diff --git a/lang/uk/settings.php b/lang/uk/settings.php index afeb2c48928..9706b328b3b 100644 --- a/lang/uk/settings.php +++ b/lang/uk/settings.php @@ -207,6 +207,7 @@ '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' => 'Наразі жоден користувач не призначений для цієї ролі', diff --git a/lang/uz/entities.php b/lang/uz/entities.php index e35a485b65d..d3fdb594421 100644 --- a/lang/uz/entities.php +++ b/lang/uz/entities.php @@ -173,6 +173,7 @@ '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' => 'Kitobni tartiblash: kitob nomi', 'books_sort_name' => 'Nomi bo\'yicha saralash', 'books_sort_created' => 'Yaratilgan sana bo\'yicha saralash', diff --git a/lang/uz/settings.php b/lang/uz/settings.php index 259aee71a3f..0dbf8351e83 100644 --- a/lang/uz/settings.php +++ b/lang/uz/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Hammasi', 'role_own' => 'Shaxsiy', 'role_controlled_by_asset' => 'Ular yuklangan obyekt tomonidan nazorat qilinadi', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Rolni saqlash', 'role_users' => 'Ushbu roldagi foydalanuvchilar', 'role_users_none' => 'Hozirda bu rolga hech qanday foydalanuvchi tayinlanmagan', diff --git a/lang/vi/entities.php b/lang/vi/entities.php index f538f993ca2..fc1ca6d5620 100644 --- a/lang/vi/entities.php +++ b/lang/vi/entities.php @@ -173,6 +173,7 @@ 'books_sort_desc' => 'Di chuyển các chương và trang trong một cuốn sách để sắp xếp lại nội dung của nó. Các sách khác có thể được thêm vào để dễ dàng di chuyển các chương và trang giữa các sách. Tùy chọn, một quy tắc sắp xếp tự động có thể được đặt để tự động sắp xếp nội dung cuốn sách này khi có thay đổi.', 'books_sort_auto_sort' => 'Tùy chọn sắp xếp tự động', 'books_sort_auto_sort_active' => 'Sắp xếp tự động đang hoạt động: :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' => 'Sắp xếp sách :bookName', 'books_sort_name' => 'Sắp xếp theo tên', 'books_sort_created' => 'Sắp xếp theo ngày tạo', diff --git a/lang/vi/settings.php b/lang/vi/settings.php index f5f2377c81b..c0bca56914c 100644 --- a/lang/vi/settings.php +++ b/lang/vi/settings.php @@ -207,6 +207,7 @@ 'role_all' => 'Tất cả', 'role_own' => 'Sở hữu', 'role_controlled_by_asset' => 'Kiểm soát các tài sản (asset) người dùng tải lên', + 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', 'role_save' => 'Lưu Quyền', 'role_users' => 'Người dùng được gán quyền này', 'role_users_none' => 'Không có người dùng nào hiện được gán quyền này', diff --git a/lang/zh_CN/entities.php b/lang/zh_CN/entities.php index c4ec0414dab..9d57eeb5719 100644 --- a/lang/zh_CN/entities.php +++ b/lang/zh_CN/entities.php @@ -173,6 +173,7 @@ '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' => '创建时间排序', diff --git a/lang/zh_CN/settings.php b/lang/zh_CN/settings.php index e53e67aba32..86dd680b02c 100644 --- a/lang/zh_CN/settings.php +++ b/lang/zh_CN/settings.php @@ -207,6 +207,7 @@ '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' => '目前没有用户被分配到这个角色', diff --git a/lang/zh_TW/entities.php b/lang/zh_TW/entities.php index 46bbe62f973..08886013c43 100644 --- a/lang/zh_TW/entities.php +++ b/lang/zh_TW/entities.php @@ -173,6 +173,7 @@ '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' => '按建立時間排序', diff --git a/lang/zh_TW/settings.php b/lang/zh_TW/settings.php index 65778f77ca3..fd5b088f5f8 100644 --- a/lang/zh_TW/settings.php +++ b/lang/zh_TW/settings.php @@ -208,6 +208,7 @@ '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' => '目前沒有使用者被分配到此角色', From 1532a99d4e0cf7e17c17d8e0f4b26c2e6a08a702 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 5 May 2026 20:35:45 +0100 Subject: [PATCH 139/204] Meta: Updated issue template labels, fixed minor issues --- .forgejo/ISSUE_TEMPLATE/api_request.yml | 2 +- .forgejo/ISSUE_TEMPLATE/bug_report.yml | 2 +- .forgejo/ISSUE_TEMPLATE/feature_request.yml | 2 +- .forgejo/ISSUE_TEMPLATE/language_request.yml | 2 +- .forgejo/ISSUE_TEMPLATE/support_request.yml | 2 +- readme.md | 2 +- tests/Api/TagsApiTest.php | 3 +-- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.forgejo/ISSUE_TEMPLATE/api_request.yml b/.forgejo/ISSUE_TEMPLATE/api_request.yml index def952c52e8..c68d262580b 100644 --- a/.forgejo/ISSUE_TEMPLATE/api_request.yml +++ b/.forgejo/ISSUE_TEMPLATE/api_request.yml @@ -1,6 +1,6 @@ name: New API Endpoint or API Ability description: Request a new endpoint or API feature be added -labels: [":nut_and_bolt: API Request"] +labels: ["Type/API Request"] body: - type: textarea id: feature diff --git a/.forgejo/ISSUE_TEMPLATE/bug_report.yml b/.forgejo/ISSUE_TEMPLATE/bug_report.yml index d301826c9c6..9e4173cc798 100644 --- a/.forgejo/ISSUE_TEMPLATE/bug_report.yml +++ b/.forgejo/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ name: Bug Report description: Create a report to help us fix bugs & issues in existing supported functionality -labels: [":bug: Bug"] +labels: ["Type/Bug Report"] body: - type: markdown attributes: diff --git a/.forgejo/ISSUE_TEMPLATE/feature_request.yml b/.forgejo/ISSUE_TEMPLATE/feature_request.yml index c1420cb1933..0d799d0a78a 100644 --- a/.forgejo/ISSUE_TEMPLATE/feature_request.yml +++ b/.forgejo/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,6 @@ name: Feature Request description: Request a new feature or idea to be added to BookStack -labels: [":hammer: Feature Request"] +labels: ["Type/Feature Request"] body: - type: textarea id: description diff --git a/.forgejo/ISSUE_TEMPLATE/language_request.yml b/.forgejo/ISSUE_TEMPLATE/language_request.yml index fad9ef1e83f..b86fb08e84f 100644 --- a/.forgejo/ISSUE_TEMPLATE/language_request.yml +++ b/.forgejo/ISSUE_TEMPLATE/language_request.yml @@ -1,6 +1,6 @@ name: Language Request description: Request a new language to be added to Crowdin for you to translate -labels: [":earth_africa: Translations"] +labels: ["Focus: Translations"] assignees: - ssddanbrown body: diff --git a/.forgejo/ISSUE_TEMPLATE/support_request.yml b/.forgejo/ISSUE_TEMPLATE/support_request.yml index d60eab71101..fde4aad7141 100644 --- a/.forgejo/ISSUE_TEMPLATE/support_request.yml +++ b/.forgejo/ISSUE_TEMPLATE/support_request.yml @@ -1,6 +1,6 @@ name: Support Request description: Request support for a specific problem you have not been able to solve yourself -labels: [":dog2: Support"] +labels: ["Type/Support"] body: - type: checkboxes id: useddocs diff --git a/readme.md b/readme.md index 30d4d17891f..e9e5d197b43 100644 --- a/readme.md +++ b/readme.md @@ -29,7 +29,7 @@ A platform for storing and organising information and documentation. Details for BookStack is an opinionated documentation platform that provides a pleasant and simple out-of-the-box experience. New users to an instance should find the experience intuitive and only basic word-processing skills should be required to get involved in creating content on BookStack. The platform should provide advanced power features to those that desire it, but they should not interfere with the core simple user experience. -BookStack is not designed as an extensible platform to be used for purposes that differ to the statement above. +BookStack is not designed as an extensible platform to be used for purposes that differ from the statement above. In regard to development philosophy, BookStack has a relaxed, open & positive approach. We aim to slowly yet continuously evolve the platform while providing a stable & easy upgrade path. diff --git a/tests/Api/TagsApiTest.php b/tests/Api/TagsApiTest.php index a079fa63915..e39d72ce10c 100644 --- a/tests/Api/TagsApiTest.php +++ b/tests/Api/TagsApiTest.php @@ -1,12 +1,11 @@ Date: Tue, 5 May 2026 20:44:04 +0100 Subject: [PATCH 140/204] Languages: Enabled Thai as a language option --- app/Translation/LocaleManager.php | 1 + lang/en/settings.php | 1 + 2 files changed, 2 insertions(+) diff --git a/app/Translation/LocaleManager.php b/app/Translation/LocaleManager.php index d23c2361004..ea32976a887 100644 --- a/app/Translation/LocaleManager.php +++ b/app/Translation/LocaleManager.php @@ -64,6 +64,7 @@ class LocaleManager 'sq' => 'sq_AL', 'sr' => 'sr_RS', 'sv' => 'sv_SE', + 'th' => 'th_TH', 'tk' => 'tk_TM', 'tr' => 'tr_TR', 'uk' => 'uk_UA', diff --git a/lang/en/settings.php b/lang/en/settings.php index 3937c650f86..3ccf15e7239 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -364,6 +364,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', From 50d3be4c95fb9ed17699a86cafc5634c22972250 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 8 May 2026 17:03:27 +0200 Subject: [PATCH 141/204] CI: Made actions more efficient (#6124) Updates our CI process to be more efficient by: - Uses setupphp/node image for more direct access to desired PHP versions. - Adds php extension caching via https://github.com/shivammathur/cache-extensions - Reverted to using MySQL in-test-container to reduce syscalls across the container stack which seemed to be slowing things down. - Update JS testing to only use one worker, to avoid exhausting all CPUs. I think it was attempting to use all threads on the host system before, causing the machine to lock up, since only a subset of cores were available to the environment. Reviewed-on: https://codeberg.org/bookstack/bookstack/pulls/6124 --- .forgejo/workflows/test-js.yml | 2 +- .forgejo/workflows/test-php.yml | 45 ++++++++++++++++++++++++--------- package.json | 3 ++- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/.forgejo/workflows/test-js.yml b/.forgejo/workflows/test-js.yml index 180e6d54501..d3e8467fe09 100644 --- a/.forgejo/workflows/test-js.yml +++ b/.forgejo/workflows/test-js.yml @@ -29,4 +29,4 @@ jobs: run: npm run ts:lint - name: Run JavaScript tests - run: npm run test \ No newline at end of file + run: npm run test:ci \ No newline at end of file diff --git a/.forgejo/workflows/test-php.yml b/.forgejo/workflows/test-php.yml index 5ff2d14a5e0..06a6de276d2 100644 --- a/.forgejo/workflows/test-php.yml +++ b/.forgejo/workflows/test-php.yml @@ -16,26 +16,36 @@ jobs: if: ${{ github.ref != 'refs/heads/l10n_development' }} runs-on: docker container: - image: docker.io/library/node:24-trixie + image: docker.io/setupphp/node:noble 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 + env: + phpextensions: gd, mbstring, json, curl, xml, 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 }} + - name: Setup PHP uses: https://github.com/shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: gd, mbstring, json, curl, xml, mysql, ldap, gmp + extensions: ${{ env.phpextensions }} - name: Get Composer Cache Directory id: composer-cache @@ -54,14 +64,25 @@ jobs: env: COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.GH_TOKEN }}"}}' + - 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: Migrate and seed the database env: - TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@mysql/bookstack-test' + 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@mysql/bookstack-test' + TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@localhost/bookstack-test' run: php${{ matrix.php }} ./vendor/bin/phpunit diff --git a/package.json b/package.json index 80810fff170..2ffebc36a77 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "lint": "eslint \"resources/**/*.js\" \"resources/**/*.mjs\"", "fix": "eslint --fix \"resources/**/*.js\" \"resources/**/*.mjs\"", "ts:lint": "tsc --noEmit", - "test": "jest" + "test": "jest", + "test:ci": "jest --maxWorkers=1" }, "devDependencies": { "@eslint/js": "^10.0.1", From 6917eaf7bd35c125304acdc20bd4c5ceff3931d4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 26 Apr 2026 11:48:05 +0100 Subject: [PATCH 142/204] Lexical: Added support for keyCode-based fallback shortcut use Helps in cases where languages like cyrillic may have the relevant key to use but the actual text/.key value is the cyrillic key value instead of the shorcut key we expect. --- resources/js/wysiwyg/services/shortcuts.ts | 70 ++++++++++++++++++---- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/resources/js/wysiwyg/services/shortcuts.ts b/resources/js/wysiwyg/services/shortcuts.ts index 00abe0c6d2f..2702b5486e2 100644 --- a/resources/js/wysiwyg/services/shortcuts.ts +++ b/resources/js/wysiwyg/services/shortcuts.ts @@ -94,30 +94,78 @@ const extendedActionsByKeys: Record = { }; function createKeyDownListener(context: EditorUiContext, useExtended: boolean): (e: KeyboardEvent) => void { - const keySetToUse = useExtended ? extendedActionsByKeys : baseActionsByKeys; + const baseKeySetToUse = useExtended ? extendedActionsByKeys : baseActionsByKeys; + const keySetToUse = extendKeySetWithKeyCodes(baseKeySetToUse); return (event: KeyboardEvent) => { - const combo = keyboardEventToKeyComboString(event); - // console.log(`pressed: ${combo}`); - if (keySetToUse[combo]) { - const handled = keySetToUse[combo](context.editor, context); - if (handled) { - event.stopPropagation(); - event.preventDefault(); + const comboStrings = keyboardEventToKeyComboStrings(event); + // console.log(comboStrings, event, keySetToUse); + for (const combo of comboStrings) { + if (keySetToUse[combo]) { + const handled = keySetToUse[combo](context.editor, context); + if (handled) { + event.stopPropagation(); + event.preventDefault(); + } + break; } } }; } -function keyboardEventToKeyComboString(event: KeyboardEvent): string { +/** + * Takes a shortcut key set and returns a new set with added variations of shortcts where + * they can be sensibly represented as their key code instead of just key, which we can use + * for matching in scenarios where the physical key may be represented of the letter used + * in the shortcut, but produces a different 'key' value. + * Useful for Cyrillic scenarios where the keyboard key would show a latin character + * as an option, and therefore be expected for use for the relevant latin shortcut, but the main + * key output is a Cyrillic character. + */ +function extendKeySetWithKeyCodes(keySet: Record): Record { + const newKeys: Record = {}; + + const setKeys = Object.keys(keySet); + for (const keyCombo of setKeys) { + const action = keySet[keyCombo]; + newKeys[keyCombo] = action; + + const comboParts = keyCombo.split('+'); + const lastComboPart = comboParts.pop() || ''; + if (lastComboPart.match(/^[a-zA-Z]$/)) { + const keyCode = lastComboPart.toUpperCase().charCodeAt(0); + comboParts.push(String(keyCode)); + const newCombo = comboParts.join('+'); + newKeys[newCombo] = action; + } + } + + return newKeys; +} + +function keyboardEventToKeyComboStrings(event: KeyboardEvent): string[] { const metaKeyPressed = isMac() ? event.metaKey : event.ctrlKey; - const parts = [ + const mainParts = [ metaKeyPressed ? 'meta' : '', event.shiftKey ? 'shift' : '', event.key, ]; - return parts.filter(Boolean).join('+').toLowerCase(); + const toReturn = [ + mainParts.filter(Boolean).join('+').toLowerCase(), + ]; + + // If ending with a standard latin character, provide an alternative + // keyCode based option for scenarios of dual-language keyboard use. + const keyCode = event.keyCode || 0; + if (keyCode >= 65 && keyCode <= 90) { + const keyCodeParts = [...mainParts]; + keyCodeParts.pop(); + keyCodeParts.push(String(keyCode)); + toReturn.push(keyCodeParts.filter(Boolean).join('+').toLowerCase()); + } + + return toReturn; } function isMac(): boolean { From f1452ebe2a70829a16a85cb50c15ec3d9c9b959e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 4 May 2026 20:52:29 +0100 Subject: [PATCH 143/204] Lexical: Improved content insert on drop handling - Adds specific support for inline content handling. - Adds attempting to use caret position at drop location for accurate placement. --- .../wysiwyg/services/drop-paste-handling.ts | 34 +++++++--------- resources/js/wysiwyg/utils/nodes.ts | 22 +++++++++++ resources/js/wysiwyg/utils/selection.ts | 39 +++++++++++++++++++ 3 files changed, 76 insertions(+), 19 deletions(-) diff --git a/resources/js/wysiwyg/services/drop-paste-handling.ts b/resources/js/wysiwyg/services/drop-paste-handling.ts index 57f9a80ae18..b63c57ada74 100644 --- a/resources/js/wysiwyg/services/drop-paste-handling.ts +++ b/resources/js/wysiwyg/services/drop-paste-handling.ts @@ -1,40 +1,35 @@ import { $createParagraphNode, $insertNodes, - $isDecoratorNode, COMMAND_PRIORITY_HIGH, DROP_COMMAND, + $isDecoratorNode, $isTextNode, $setSelection, COMMAND_PRIORITY_HIGH, DROP_COMMAND, LexicalEditor, LexicalNode, PASTE_COMMAND } from "lexical"; -import {$insertNewBlockNodesAtSelection, $selectSingleNode} from "../utils/selection"; -import {$getNearestBlockNodeForCoords, $htmlToBlockNodes} from "../utils/nodes"; +import {$insertNewNodesAtSelection, $selectSingleNode} from "../utils/selection"; +import {$getNodePositionFromMouseEvent, $htmlToBlockNodes} from "../utils/nodes"; import {Clipboard} from "../../services/clipboard"; import {$createImageNode} from "@lexical/rich-text/LexicalImageNode"; import {$createLinkNode} from "@lexical/link"; import {EditorImageData, uploadImageFile} from "../utils/images"; import {EditorUiContext} from "../ui/framework/core"; -function $getNodeFromMouseEvent(event: MouseEvent, editor: LexicalEditor): LexicalNode|null { - const x = event.clientX; - const y = event.clientY; - const dom = document.elementFromPoint(x, y); - if (!dom) { - return null; - } - - return $getNearestBlockNodeForCoords(editor, event.clientX, event.clientY); -} function $insertNodesAtEvent(nodes: LexicalNode[], event: DragEvent, editor: LexicalEditor) { - const positionNode = $getNodeFromMouseEvent(event, editor); + const position = $getNodePositionFromMouseEvent(event, editor); - if (positionNode) { - $selectSingleNode(positionNode); + if (position && $isTextNode(position.node)) { + const selection = position.node.select(position.offset, position.offset); + $setSelection(selection); + } else if (position) { + $selectSingleNode(position.node); } - $insertNewBlockNodesAtSelection(nodes, true); + $insertNewNodesAtSelection(nodes); - if (!$isDecoratorNode(positionNode) || !positionNode?.getTextContent()) { - positionNode?.remove(); + if (position) { + if (!$isDecoratorNode(position.node) && !position.node?.getTextContent()) { + position.node.remove(); + } } } @@ -113,6 +108,7 @@ function handleImageLinkInsert(data: DataTransfer, context: EditorUiContext): bo function createDropListener(context: EditorUiContext): (event: DragEvent) => boolean { const editor = context.editor; return (event: DragEvent): boolean => { + // Template handling const templateId = event.dataTransfer?.getData('bookstack/template') || ''; if (templateId) { diff --git a/resources/js/wysiwyg/utils/nodes.ts b/resources/js/wysiwyg/utils/nodes.ts index ed70bf6996e..eab8203a0b2 100644 --- a/resources/js/wysiwyg/utils/nodes.ts +++ b/resources/js/wysiwyg/utils/nodes.ts @@ -1,5 +1,6 @@ import { $createParagraphNode, + $getNearestNodeFromDOMNode, $getRoot, $isDecoratorNode, $isElementNode, $isRootNode, @@ -64,6 +65,27 @@ export function $getAllNodesOfType(matcher: LexicalNodeMatcher, root?: ElementNo return matches; } +/** + * Get the node based on the given mouse event. + */ +export function $getNodePositionFromMouseEvent(event: MouseEvent, editor: LexicalEditor): {node: LexicalNode, offset: number}|null { + const x = event.clientX; + const y = event.clientY; + const caretPosition = window.document.caretPositionFromPoint(event.x, event.y); + if (!caretPosition) { + const backup = $getNearestBlockNodeForCoords(editor, x, y); + return backup ? {node: backup, offset: 0} : null; + } + + const node = $getNearestNodeFromDOMNode(caretPosition.offsetNode); + if (!node) { + const backup = $getNearestBlockNodeForCoords(editor, x, y); + return backup ? {node: backup, offset: 0} : null; + } + + return {node, offset: caretPosition.offset}; +} + /** * Get the nearest root/block level node for the given position. */ diff --git a/resources/js/wysiwyg/utils/selection.ts b/resources/js/wysiwyg/utils/selection.ts index e4b5bf2dce6..f5d3699fcbf 100644 --- a/resources/js/wysiwyg/utils/selection.ts +++ b/resources/js/wysiwyg/utils/selection.ts @@ -109,6 +109,45 @@ export function $insertNewBlockNodesAtSelection(nodes: LexicalNode[], insertAfte } } +export function $insertNewNodesAtSelection(nodes: LexicalNode[]) { + const selection = $getSelection(); + const selectionPoints = selection?.getStartEndPoints(); + let target: LexicalNode|null = null; + let targetBlock: LexicalNode|null = null; + if (selectionPoints) { + const selectionEnd = selectionPoints[1]; + target = selectionEnd.getNode(); + targetBlock = target ? $getNearestNodeBlockParent(target) : null; + } + + for (const node of nodes) { + const isBlock = $isBlockElementNode(node); + + if (isBlock && !targetBlock) { + // Append to the root if its a block and we can't determine position + $getRoot().append(node); + } else if (isBlock && targetBlock) { + // Insert after the target block if we have a block + targetBlock.insertAfter(node); + } else if (!isBlock && selection) { + // Insert at selection if likely inline + selection.insertNodes(nodes); + } else if (!isBlock && $isElementNode(targetBlock)) { + // Append inside the target block if inline but we don't have + // a selection (typically used by the case below) + targetBlock.append(node); + } else { + // Otherwise (where inline) create a new root level paragraph + // and insert content into that. Update the target block + // for re-use by other inline elements. + const paragraph = $createParagraphNode(); + paragraph.append(node); + $getRoot().append(paragraph); + targetBlock = paragraph; + } + } +} + export function $selectSingleNode(node: LexicalNode) { const nodeSelection = $createNodeSelection(); nodeSelection.add(node.getKey()); From 0eed869735eec0f26bb911cd742ad74ccb63e5e4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 4 May 2026 22:32:19 +0100 Subject: [PATCH 144/204] Lexical: Fixed in-editor content drag and drop Now more reliable and aligned to expectations, instead of loosing information and/or deleting elements, or inserting above/below blocks. --- .../wysiwyg/services/drop-paste-handling.ts | 61 ++++++++++++++++--- resources/js/wysiwyg/utils/selection.ts | 36 ++++------- 2 files changed, 66 insertions(+), 31 deletions(-) diff --git a/resources/js/wysiwyg/services/drop-paste-handling.ts b/resources/js/wysiwyg/services/drop-paste-handling.ts index b63c57ada74..e11d6bd5e83 100644 --- a/resources/js/wysiwyg/services/drop-paste-handling.ts +++ b/resources/js/wysiwyg/services/drop-paste-handling.ts @@ -1,18 +1,21 @@ import { - $createParagraphNode, + $createParagraphNode, $getSelection, $insertNodes, - $isDecoratorNode, $isTextNode, $setSelection, COMMAND_PRIORITY_HIGH, DROP_COMMAND, + $isDecoratorNode, + $isRangeSelection, $isTextNode, $setSelection, COMMAND_PRIORITY_HIGH, DRAGSTART_COMMAND, DROP_COMMAND, LexicalEditor, LexicalNode, PASTE_COMMAND } from "lexical"; -import {$insertNewNodesAtSelection, $selectSingleNode} from "../utils/selection"; -import {$getNodePositionFromMouseEvent, $htmlToBlockNodes} from "../utils/nodes"; +import {$getBlockElementNodesInSelection, $insertNewNodesAtSelection, $selectSingleNode} from "../utils/selection"; +import {$getNodePositionFromMouseEvent, $htmlToBlockNodes, $htmlToNodes} from "../utils/nodes"; import {Clipboard} from "../../services/clipboard"; import {$createImageNode} from "@lexical/rich-text/LexicalImageNode"; import {$createLinkNode} from "@lexical/link"; import {EditorImageData, uploadImageFile} from "../utils/images"; import {EditorUiContext} from "../ui/framework/core"; +import {$getHtmlContent} from "@lexical/clipboard"; +const internalActiveDragTracker: WeakMap = new WeakMap(); function $insertNodesAtEvent(nodes: LexicalNode[], event: DragEvent, editor: LexicalEditor) { const position = $getNodePositionFromMouseEvent(event, editor); @@ -44,6 +47,26 @@ async function insertTemplateToEditor(editor: LexicalEditor, templateId: string, }); } +function insertHtmlToEditor(editor: LexicalEditor, html: string, isFromInternal: boolean, event: DragEvent) { + editor.update(() => { + if (isFromInternal) { + const selected = $getSelection(); + if ($isRangeSelection(selected)) { + selected.removeText(); + const selectionBlocks = $getBlockElementNodesInSelection(selected); + for (const block of selectionBlocks) { + if (block.isEmpty()) { + block.remove(); + } + } + } + } + + const newNodes = $htmlToNodes(editor, html); + $insertNodesAtEvent(newNodes, event, editor); + }); +} + function handleMediaInsert(data: DataTransfer, context: EditorUiContext): boolean { const clipboard = new Clipboard(data); let handled = false; @@ -109,6 +132,9 @@ function createDropListener(context: EditorUiContext): (event: DragEvent) => boo const editor = context.editor; return (event: DragEvent): boolean => { + const hadInternalActiveDrag = internalActiveDragTracker.has(editor); + internalActiveDragTracker.delete(editor); + // Template handling const templateId = event.dataTransfer?.getData('bookstack/template') || ''; if (templateId) { @@ -121,10 +147,7 @@ function createDropListener(context: EditorUiContext): (event: DragEvent) => boo // HTML contents drop const html = event.dataTransfer?.getData('text/html') || ''; if (html) { - editor.update(() => { - const newNodes = $htmlToBlockNodes(editor, html); - $insertNodesAtEvent(newNodes, event, editor); - }); + insertHtmlToEditor(editor, html, hadInternalActiveDrag, event); event.preventDefault(); event.stopPropagation(); return true; @@ -161,17 +184,39 @@ function createPasteListener(context: EditorUiContext): (event: ClipboardEvent) }; } +function createDragStartListener(context: EditorUiContext): (event: DragEvent) => boolean { + return (event: DragEvent) => { + // Track when drag events are started internally from the editor + internalActiveDragTracker.set(context.editor, event); + + // If an internal range selection, serialize the range contents + // fully as output HTML, instead of editor HTML + context.editor.update(() => { + const selection = $getSelection(); + if ($isRangeSelection(selection)) { + selection.extract(); + const html = $getHtmlContent(context.editor, selection); + event.dataTransfer?.setData('text/html', html); + } + }); + return false; + }; +} + export function registerDropPasteHandling(context: EditorUiContext): () => void { const dropListener = createDropListener(context); const pasteListener = createPasteListener(context); + const dragstartListener = createDragStartListener(context); const unregisterDrop = context.editor.registerCommand(DROP_COMMAND, dropListener, COMMAND_PRIORITY_HIGH); const unregisterPaste = context.editor.registerCommand(PASTE_COMMAND, pasteListener, COMMAND_PRIORITY_HIGH); + const unregisterDragStart = context.editor.registerCommand(DRAGSTART_COMMAND, dragstartListener, COMMAND_PRIORITY_HIGH); context.scrollDOM.addEventListener('drop', dropListener); return () => { unregisterDrop(); unregisterPaste(); + unregisterDragStart(); context.scrollDOM.removeEventListener('drop', dropListener); }; } \ No newline at end of file diff --git a/resources/js/wysiwyg/utils/selection.ts b/resources/js/wysiwyg/utils/selection.ts index f5d3699fcbf..28050571ede 100644 --- a/resources/js/wysiwyg/utils/selection.ts +++ b/resources/js/wysiwyg/utils/selection.ts @@ -111,38 +111,28 @@ export function $insertNewBlockNodesAtSelection(nodes: LexicalNode[], insertAfte export function $insertNewNodesAtSelection(nodes: LexicalNode[]) { const selection = $getSelection(); - const selectionPoints = selection?.getStartEndPoints(); - let target: LexicalNode|null = null; - let targetBlock: LexicalNode|null = null; - if (selectionPoints) { - const selectionEnd = selectionPoints[1]; - target = selectionEnd.getNode(); - targetBlock = target ? $getNearestNodeBlockParent(target) : null; + if (selection) { + selection.insertNodes(nodes); + return; } + // Do something relatively sensible if we don't have a selection within view + const root = $getRoot(); + let targetBlock = root.getLastChild(); for (const node of nodes) { const isBlock = $isBlockElementNode(node); - if (isBlock && !targetBlock) { - // Append to the root if its a block and we can't determine position - $getRoot().append(node); - } else if (isBlock && targetBlock) { - // Insert after the target block if we have a block - targetBlock.insertAfter(node); - } else if (!isBlock && selection) { - // Insert at selection if likely inline - selection.insertNodes(nodes); - } else if (!isBlock && $isElementNode(targetBlock)) { - // Append inside the target block if inline but we don't have - // a selection (typically used by the case below) + root.append(node); + targetBlock = node; + } else if (isBlock) { + targetBlock?.insertAfter(node); + targetBlock = node; + } else if ($isElementNode(targetBlock)) { targetBlock.append(node); } else { - // Otherwise (where inline) create a new root level paragraph - // and insert content into that. Update the target block - // for re-use by other inline elements. const paragraph = $createParagraphNode(); paragraph.append(node); - $getRoot().append(paragraph); + root.append(paragraph); targetBlock = paragraph; } } From df831a0564bee28294e093f8a9e509c1528e8ddf Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 5 May 2026 16:37:24 +0100 Subject: [PATCH 145/204] Lexical: Added RTL support for UI dropdown menus They now show in the correct direction and do not overlap. Added new helper for RTL bounding box handling. --- .../ui/framework/blocks/table-creator.ts | 2 +- .../wysiwyg/ui/framework/helpers/dropdowns.ts | 44 ++++++++++------- resources/js/wysiwyg/ui/framework/manager.ts | 1 + resources/js/wysiwyg/utils/rtl.ts | 49 +++++++++++++++++++ 4 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 resources/js/wysiwyg/utils/rtl.ts diff --git a/resources/js/wysiwyg/ui/framework/blocks/table-creator.ts b/resources/js/wysiwyg/ui/framework/blocks/table-creator.ts index 6f026ca1895..03e55ce64e8 100644 --- a/resources/js/wysiwyg/ui/framework/blocks/table-creator.ts +++ b/resources/js/wysiwyg/ui/framework/blocks/table-creator.ts @@ -27,7 +27,7 @@ export class EditorTableCreator extends EditorUiElement { }, rowCells)); } - const display = el('div', {class: 'editor-table-creator-display'}, ['0 x 0']); + const display = el('div', {class: 'editor-table-creator-display', dir: 'ltr'}, ['0 x 0']); const grid = el('div', {class: 'editor-table-creator-grid'}, rows); grid.addEventListener('mousemove', event => { const cell = (event.target as HTMLElement).closest('.editor-table-creator-cell') as HTMLElement|null; diff --git a/resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts b/resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts index 890d5b325fe..f86482fdc2d 100644 --- a/resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts +++ b/resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts @@ -1,3 +1,5 @@ +import {getViewportRect, RTLRect} from "../../../utils/rtl"; + interface HandleDropdownParams { toggle: HTMLElement; menu: HTMLElement; @@ -7,30 +9,31 @@ interface HandleDropdownParams { showAside?: boolean; } -function positionMenu(menu: HTMLElement, toggle: HTMLElement, showAside: boolean) { - const toggleRect = toggle.getBoundingClientRect(); - const menuBounds = menu.getBoundingClientRect(); +function positionMenu(menu: HTMLElement, toggle: HTMLElement, showAside: boolean, isRTL: boolean) { + const toggleRect = new RTLRect(toggle, isRTL); + const menuBounds = new RTLRect(menu, isRTL); + const viewport = getViewportRect(); menu.style.position = 'fixed'; if (showAside) { - let targetLeft = toggleRect.right; - const isRightOOB = toggleRect.right + menuBounds.width > window.innerWidth; - if (isRightOOB) { - targetLeft = Math.max(toggleRect.left - menuBounds.width, 0); + let targetLeft = toggleRect.inlineEnd; + const isEndOOB = toggleRect.inlineEnd + menuBounds.width > viewport.width; + if (isEndOOB) { + targetLeft = Math.max(toggleRect.inlineStart - menuBounds.width, 0); } - menu.style.top = toggleRect.top + 'px'; - menu.style.left = targetLeft + 'px'; + menu.style.top = toggleRect.blockStart + 'px'; + menu.style.insetInlineStart = targetLeft + 'px'; } else { - const isRightOOB = toggleRect.left + menuBounds.width > window.innerWidth; - let targetLeft = toggleRect.left; - if (isRightOOB) { - targetLeft = Math.max(toggleRect.right - menuBounds.width, 0); + const isEndOOB = toggleRect.inlineStart + menuBounds.width > viewport.width; + let targetLeft = toggleRect.inlineStart; + if (isEndOOB) { + targetLeft = Math.max(toggleRect.inlineEnd - menuBounds.width, 0); } - menu.style.top = toggleRect.bottom + 'px'; - menu.style.left = targetLeft + 'px'; + menu.style.top = toggleRect.blockEnd + 'px'; + menu.style.insetInlineStart = targetLeft + 'px'; } } @@ -38,6 +41,7 @@ export class DropDownManager { protected dropdownOptions: WeakMap = new WeakMap(); protected openDropdowns: Set = new Set(); + protected isRTL: boolean = false; constructor() { this.onMenuMouseOver = this.onMenuMouseOver.bind(this); @@ -46,6 +50,10 @@ export class DropDownManager { window.addEventListener('click', this.onWindowClick); } + setIsRTL(isRTL: boolean): void { + this.isRTL = isRTL; + } + teardown(): void { window.removeEventListener('click', this.onWindowClick); } @@ -80,7 +88,7 @@ export class DropDownManager { protected closeDropdown(menu: HTMLElement): void { menu.hidden = true; menu.style.removeProperty('position'); - menu.style.removeProperty('left'); + menu.style.removeProperty('inset-inline-start'); menu.style.removeProperty('top'); this.openDropdowns.delete(menu); @@ -94,8 +102,8 @@ export class DropDownManager { protected openDropdown(menu: HTMLElement): void { const {toggle, showAside, onOpen} = this.getOptions(menu); - menu.hidden = false - positionMenu(menu, toggle, Boolean(showAside)); + menu.hidden = false; + positionMenu(menu, toggle, Boolean(showAside), this.isRTL); this.openDropdowns.add(menu); menu.addEventListener('mouseover', this.onMenuMouseOver); diff --git a/resources/js/wysiwyg/ui/framework/manager.ts b/resources/js/wysiwyg/ui/framework/manager.ts index 3b4d5b495a8..f8525bc7c69 100644 --- a/resources/js/wysiwyg/ui/framework/manager.ts +++ b/resources/js/wysiwyg/ui/framework/manager.ts @@ -30,6 +30,7 @@ export class EditorUIManager { this.context = context; this.setupEventListeners(); this.setupEditor(context.editor, context); + this.dropdowns.setIsRTL(this.context.manager.getDefaultDirection() === 'rtl'); } getContext(): EditorUiContext { diff --git a/resources/js/wysiwyg/utils/rtl.ts b/resources/js/wysiwyg/utils/rtl.ts new file mode 100644 index 00000000000..e04554f34b9 --- /dev/null +++ b/resources/js/wysiwyg/utils/rtl.ts @@ -0,0 +1,49 @@ +/** + * Create a viewport relative rect for an element which provides + * logical property support. + */ +export class RTLRect { + protected rect: DOMRect; + protected isRTL: boolean; + + constructor(element: HTMLElement, isRTL = false) { + this.rect = element.getBoundingClientRect(); + this.isRTL = isRTL; + } + + get blockStart(): number { + return this.rect.top; + } + + get inlineStart(): number { + if (!this.isRTL) { + return this.rect.left; + } + + return window.innerWidth - this.rect.right; + } + + get blockEnd(): number { + return this.rect.bottom; + } + + get inlineEnd(): number { + if (!this.isRTL) { + return this.rect.right; + } + + return window.innerWidth - this.rect.left; + } + + get width(): number { + return this.rect.width; + } + + get height(): number { + return this.rect.height; + } +} + +export function getViewportRect(): RTLRect { + return new RTLRect(document.documentElement, false); +} From b794f749ddbfb9d84ed53602f18b92c63d3e6e6f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 5 May 2026 20:03:53 +0100 Subject: [PATCH 146/204] Lexical: Updated core inline formats to instead custom built handler Aligns logic used for shortcut handling, so enables these to work for cyrillic equivilent keyboard keys. --- .../js/wysiwyg/lexical/core/LexicalEvents.ts | 13 -------- .../js/wysiwyg/lexical/core/LexicalUtils.ts | 33 ------------------- resources/js/wysiwyg/services/shortcuts.ts | 20 ++++++++--- 3 files changed, 15 insertions(+), 51 deletions(-) diff --git a/resources/js/wysiwyg/lexical/core/LexicalEvents.ts b/resources/js/wysiwyg/lexical/core/LexicalEvents.ts index 2d197ccc27a..7d87d9055b6 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalEvents.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalEvents.ts @@ -99,7 +99,6 @@ import { getNearestEditorFromDOMNode, getWindow, isAt, isBackspace, - isBold, isCopy, isCut, isDelete, @@ -111,7 +110,6 @@ import { isDeleteWordForward, isEscape, isFirefoxClipboardEvents, - isItalic, isLexicalEditor, isLineBreak, isModifier, @@ -128,7 +126,6 @@ import { isSelectionWithinEditor, isSpace, isTab, - isUnderline, isUndo, } from './LexicalUtils'; @@ -479,7 +476,6 @@ function onClick(event: PointerEvent, editor: LexicalEditor): void { } function onPointerDown(event: PointerEvent, editor: LexicalEditor) { - // TODO implement text drag & drop const target = event.target; const pointerType = event.pointerType; if (target instanceof Node && pointerType !== 'touch') { @@ -1064,15 +1060,6 @@ function onKeyDown(event: KeyboardEvent, editor: LexicalEditor): void { dispatchCommand(editor, DELETE_LINE_COMMAND, false); } else if (isAt(key)) { dispatchCommand(editor, KEY_AT_COMMAND, event); - } else if (isBold(key, altKey, metaKey, ctrlKey)) { - event.preventDefault(); - dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'bold'); - } else if (isUnderline(key, altKey, metaKey, ctrlKey)) { - event.preventDefault(); - dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'underline'); - } else if (isItalic(key, altKey, metaKey, ctrlKey)) { - event.preventDefault(); - dispatchCommand(editor, FORMAT_TEXT_COMMAND, 'italic'); } else if (isTab(key, altKey, ctrlKey, metaKey)) { dispatchCommand(editor, KEY_TAB_COMMAND, event); } else if (isUndo(key, shiftKey, metaKey, ctrlKey)) { diff --git a/resources/js/wysiwyg/lexical/core/LexicalUtils.ts b/resources/js/wysiwyg/lexical/core/LexicalUtils.ts index b0bf2f180bc..cff86a74a2b 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalUtils.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalUtils.ts @@ -783,39 +783,6 @@ export function isTab( return key === 'Tab' && !altKey && !ctrlKey && !metaKey; } -export function isBold( - key: string, - altKey: boolean, - metaKey: boolean, - ctrlKey: boolean, -): boolean { - return ( - key.toLowerCase() === 'b' && !altKey && controlOrMeta(metaKey, ctrlKey) - ); -} - -export function isItalic( - key: string, - altKey: boolean, - metaKey: boolean, - ctrlKey: boolean, -): boolean { - return ( - key.toLowerCase() === 'i' && !altKey && controlOrMeta(metaKey, ctrlKey) - ); -} - -export function isUnderline( - key: string, - altKey: boolean, - metaKey: boolean, - ctrlKey: boolean, -): boolean { - return ( - key.toLowerCase() === 'u' && !altKey && controlOrMeta(metaKey, ctrlKey) - ); -} - export function isParagraph(key: string, shiftKey: boolean): boolean { return isReturn(key) && !shiftKey; } diff --git a/resources/js/wysiwyg/services/shortcuts.ts b/resources/js/wysiwyg/services/shortcuts.ts index 2702b5486e2..f666657038b 100644 --- a/resources/js/wysiwyg/services/shortcuts.ts +++ b/resources/js/wysiwyg/services/shortcuts.ts @@ -1,4 +1,11 @@ -import {$getSelection, COMMAND_PRIORITY_HIGH, FORMAT_TEXT_COMMAND, KEY_ENTER_COMMAND, LexicalEditor} from "lexical"; +import { + $getSelection, + COMMAND_PRIORITY_HIGH, + FORMAT_TEXT_COMMAND, + KEY_ENTER_COMMAND, + LexicalEditor, + TextFormatType +} from "lexical"; import { cycleSelectionCalloutFormats, formatCodeBlock, insertOrUpdateLink, @@ -27,8 +34,8 @@ function wrapFormatAction(formatAction: (editor: LexicalEditor) => any): Shortcu }; } -function toggleInlineCode(editor: LexicalEditor): boolean { - editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'code'); +function toggleInlineFormat(editor: LexicalEditor, format: TextFormatType): boolean { + editor.dispatchCommand(FORMAT_TEXT_COMMAND, format); return true; } @@ -39,8 +46,11 @@ type ShortcutAction = (editor: LexicalEditor, context: EditorUiContext) => boole * We use "meta" as an abstraction for ctrl/cmd depending on platform. */ const baseActionsByKeys: Record = { - 'meta+8': toggleInlineCode, - 'meta+shift+e': toggleInlineCode, + 'meta+8': (e) => toggleInlineFormat(e, 'code'), + 'meta+shift+e': (e) => toggleInlineFormat(e, 'code'), + 'meta+b': (e) => toggleInlineFormat(e, 'bold'), + 'meta+i': (e) => toggleInlineFormat(e, 'italic'), + 'meta+u': (e) => toggleInlineFormat(e, 'underline'), 'meta+o': wrapFormatAction((e) => toggleSelectionAsList(e, 'number')), 'meta+p': wrapFormatAction((e) => toggleSelectionAsList(e, 'bullet')), 'meta+k': (editor, context) => { From d6b114de7489ca4df591a79b312671555b9e4e14 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 6 May 2026 00:25:43 +0100 Subject: [PATCH 147/204] Lexical: Added some test coverage for shortcut handling Updates existing keydown test helper to accept other event options. --- .../lexical/core/__tests__/utils/index.ts | 15 ++- .../services/__tests__/shortcuts.test.ts | 91 +++++++++++++++++++ 2 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 resources/js/wysiwyg/services/__tests__/shortcuts.test.ts diff --git a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts index ab54bdb31d5..a30c86fcc71 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts @@ -837,22 +837,31 @@ function formatHtml(s: string): string { return s.replace(/>\s+<').replace(/\s*\n\s*/g, ' ').trim(); } -export function dispatchKeydownEventForNode(node: LexicalNode, editor: LexicalEditor, key: string) { +interface TestKeyboardEventOptions { + ctrlKey?: boolean; + altKey?: boolean; + shiftKey?: boolean; + metaKey?: boolean; + keyCode?: number; +} + +export function dispatchKeydownEventForNode(node: LexicalNode, editor: LexicalEditor, key: string, options: TestKeyboardEventOptions = {}) { const nodeDomEl = editor.getElementByKey(node.getKey()); const event = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key, + ...options, }); nodeDomEl?.dispatchEvent(event); editor.commitUpdates(); } -export function dispatchKeydownEventForSelectedNode(editor: LexicalEditor, key: string) { +export function dispatchKeydownEventForSelectedNode(editor: LexicalEditor, key: string, options: TestKeyboardEventOptions = {}) { editor.getEditorState().read((): void => { const node = $getSelection()?.getNodes()[0] || null; if (node) { - dispatchKeydownEventForNode(node, editor, key); + dispatchKeydownEventForNode(node, editor, key, options); } }); } diff --git a/resources/js/wysiwyg/services/__tests__/shortcuts.test.ts b/resources/js/wysiwyg/services/__tests__/shortcuts.test.ts new file mode 100644 index 00000000000..f7e7298e829 --- /dev/null +++ b/resources/js/wysiwyg/services/__tests__/shortcuts.test.ts @@ -0,0 +1,91 @@ +import { + createTestContext, destroyFromContext, + dispatchKeydownEventForSelectedNode, expectEditorStateJSONPropToEqual, +} from "lexical/__tests__/utils"; +import { + $createParagraphNode, $createTextNode, + $getRoot, IS_BOLD, LexicalEditor, +} from "lexical"; +import {registerRichText} from "@lexical/rich-text"; +import {EditorUiContext} from "../../ui/framework/core"; +import {registerShortcuts} from "../shortcuts"; + +describe('Keyboard-handling service tests', () => { + + let context!: EditorUiContext; + let editor!: LexicalEditor; + + beforeEach(() => { + context = createTestContext(); + editor = context.editor; + registerRichText(editor); + registerShortcuts(context, true); + }); + + afterEach(() => { + destroyFromContext(context); + }); + + test('Basic block format shortcuts works', () => { + editor.updateAndCommit(() => { + const p = $createParagraphNode(); + p.append($createTextNode('Hello World')) + $getRoot().append(p); + p.select(); + }); + + dispatchKeydownEventForSelectedNode(editor, '1', {ctrlKey: true}); + + expectEditorStateJSONPropToEqual(editor, '0.type', 'heading'); + expectEditorStateJSONPropToEqual(editor, '0.tag', 'h2'); + + dispatchKeydownEventForSelectedNode(editor, '2', {ctrlKey: true}); + + expectEditorStateJSONPropToEqual(editor, '0.type', 'heading'); + expectEditorStateJSONPropToEqual(editor, '0.tag', 'h3'); + + dispatchKeydownEventForSelectedNode(editor, 'd', {ctrlKey: true}); + + expectEditorStateJSONPropToEqual(editor, '0.type', 'paragraph'); + expectEditorStateJSONPropToEqual(editor, '0.0.text', 'Hello World'); + }); + + test('Basic bold format shortcut works', () => { + editor.updateAndCommit(() => { + const p = $createParagraphNode(); + const text = $createTextNode('Hello World'); + p.append(text) + $getRoot().append(p); + text.select(0, 5); + }); + + // Toggle bold for selection + dispatchKeydownEventForSelectedNode(editor, 'b', {ctrlKey: true}); + expectEditorStateJSONPropToEqual(editor, '0.0.format', IS_BOLD); + expectEditorStateJSONPropToEqual(editor, '0.1.format', 0); + + // Untoggle bold for selection + dispatchKeydownEventForSelectedNode(editor, 'b', {ctrlKey: true}); + expectEditorStateJSONPropToEqual(editor, '0.0.format', 0); + }); + + test('Basic bold format shortcut works when using cyrillic equivalent keys', () => { + editor.updateAndCommit(() => { + const p = $createParagraphNode(); + const text = $createTextNode('Hello World'); + p.append(text) + $getRoot().append(p); + text.select(0, 5); + }); + + // Toggle bold for selection + dispatchKeydownEventForSelectedNode(editor, 'и', {ctrlKey: true, keyCode: 66}); + expectEditorStateJSONPropToEqual(editor, '0.0.format', IS_BOLD); + expectEditorStateJSONPropToEqual(editor, '0.1.format', 0); + + // Untoggle bold for selection + dispatchKeydownEventForSelectedNode(editor, 'и', {ctrlKey: true, keyCode: 66}); + expectEditorStateJSONPropToEqual(editor, '0.0.format', 0); + }); + +}); From dc8f80365cf6e540faccda26079e64c31fc76c6f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 9 May 2026 12:25:49 +0100 Subject: [PATCH 148/204] Lexical: Added missing table header row toggle button Also updated DOM converstion, and CSS, to make lexical format (th inside tbody) somewhat compatible/convertible with the tinymce format (td inside thead). --- resources/icons/editor/table-header.svg | 1 + .../lexical/table/LexicalTableCellNode.ts | 9 ++++--- .../js/wysiwyg/ui/defaults/buttons/tables.ts | 27 +++++++++++++++++-- resources/js/wysiwyg/ui/defaults/toolbars.ts | 15 ++++++++++- resources/js/wysiwyg/utils/tables.ts | 20 +++++++++++--- resources/sass/_tables.scss | 13 ++++++--- 6 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 resources/icons/editor/table-header.svg diff --git a/resources/icons/editor/table-header.svg b/resources/icons/editor/table-header.svg new file mode 100644 index 00000000000..e3cfcbb6344 --- /dev/null +++ b/resources/icons/editor/table-header.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts b/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts index 1c9d7ecf692..0e714e70fc2 100644 --- a/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts +++ b/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts @@ -334,10 +334,13 @@ export function $convertTableCellNodeElement( width = parseFloat(domNode_.style.width); } + let isHeader = nodeName === 'th'; + if (domNode instanceof HTMLElement && domNode.closest('thead')) { + isHeader = true; + } + const tableCellNode = $createTableCellNode( - nodeName === 'th' - ? TableCellHeaderStates.ROW - : TableCellHeaderStates.NO_STATUS, + isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS, domNode_.colSpan, width, ); diff --git a/resources/js/wysiwyg/ui/defaults/buttons/tables.ts b/resources/js/wysiwyg/ui/defaults/buttons/tables.ts index 2e4883d88e6..0e01b301c48 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/tables.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/tables.ts @@ -7,6 +7,7 @@ import insertColumnAfterIcon from "@icons/editor/table-insert-column-after.svg"; import insertColumnBeforeIcon from "@icons/editor/table-insert-column-before.svg"; import insertRowAboveIcon from "@icons/editor/table-insert-row-above.svg"; import insertRowBelowIcon from "@icons/editor/table-insert-row-below.svg"; +import tableHeaderIcon from "@icons/editor/table-header.svg"; import {EditorUiContext} from "../../framework/core"; import {$getSelection, BaseSelection} from "lexical"; import { @@ -14,7 +15,7 @@ import { $deleteTableRow__EXPERIMENTAL, $insertTableColumn__EXPERIMENTAL, $insertTableRow__EXPERIMENTAL, $isTableCellNode, - $isTableNode, $isTableRowNode, $isTableSelection, $unmergeCell, TableCellNode, + $isTableNode, $isTableRowNode, $isTableSelection, $unmergeCell, TableCellHeaderStates, TableCellNode, } from "@lexical/table"; import {$getNodeFromSelection, $selectionContainsNodeType} from "../../../utils/selection"; import {$getParentOfType} from "../../../utils/nodes"; @@ -23,7 +24,7 @@ import { $clearTableFormatting, $clearTableSizes, $getTableFromSelection, $getTableRowsFromSelection, - $mergeTableCellsInSelection + $mergeTableCellsInSelection, $toggleRowCellHeaderState } from "../../../utils/tables"; import { $copySelectedColumnsToClipboard, @@ -239,6 +240,28 @@ export const pasteRowAfter: EditorButtonDefinition = { isDisabled: (selection) => cellNotSelected(selection) || isRowClipboardEmpty(), }; +export const toggleRowHeaders: EditorButtonDefinition = { + label: 'Row header', + format: 'small', + icon: tableHeaderIcon, + action(context: EditorUiContext, button) { + context.editor.update(() => { + const row = $getNodeFromSelection($getSelection(), $isTableCellNode)?.getParent(); + if (!$isTableRowNode(row)) { + return; + } + + const isNowHeader = $toggleRowCellHeaderState(row); + button.setActiveState(isNowHeader); + }); + }, + isActive: (selection) => { + return $selectionContainsNodeType(selection, (node) => { + return $isTableCellNode(node) && node.getHeaderStyles() !== TableCellHeaderStates.NO_STATUS; + }); + } +}; + export const cutColumn: EditorButtonDefinition = { label: 'Cut column', format: 'long', diff --git a/resources/js/wysiwyg/ui/defaults/toolbars.ts b/resources/js/wysiwyg/ui/defaults/toolbars.ts index a3ada5c89f6..99d3b96d471 100644 --- a/resources/js/wysiwyg/ui/defaults/toolbars.ts +++ b/resources/js/wysiwyg/ui/defaults/toolbars.ts @@ -28,7 +28,7 @@ import { pasteRowBefore, resizeTableToContents, rowProperties, splitCell, - table, tableProperties + table, tableProperties, toggleRowHeaders } from "./buttons/tables"; import {about, fullscreen, redo, source, undo} from "./buttons/controls"; import { @@ -284,6 +284,19 @@ export const contextToolbars: Record = { return originalTarget.closest('table') as HTMLTableElement; } }, + table_header: { + selector: 'table tr:first-of-type td, table tr:first-of-type th', + content() { + return [ + new EditorOverflowContainer('table_headers', 1, [ + new EditorButton(toggleRowHeaders), + ]), + ]; + }, + displayTargetLocator(originalTarget: HTMLElement): HTMLElement { + return originalTarget.closest('table') as HTMLTableElement; + } + }, details: { selector: 'details', content() { diff --git a/resources/js/wysiwyg/utils/tables.ts b/resources/js/wysiwyg/utils/tables.ts index 15cc3cbbeb8..b835614ab3b 100644 --- a/resources/js/wysiwyg/utils/tables.ts +++ b/resources/js/wysiwyg/utils/tables.ts @@ -3,7 +3,7 @@ import { $isTableCellNode, $isTableNode, $isTableRowNode, - $isTableSelection, TableCellNode, TableNode, + $isTableSelection, TableCellHeaderStates, TableCellNode, TableNode, TableRowNode, TableSelection, } from "@lexical/table"; @@ -328,9 +328,21 @@ export function $getCellPaddingForTable(table: TableNode): string { return padding || ''; } - - - +/** + * Toggle the header state of the cells in the provided row. + * Returns a boolean to indicate if the new state of the cells is as headers. + */ +export function $toggleRowCellHeaderState(row: TableRowNode): boolean { + const firstCell = row.getFirstChild(); + const isHeader = $isTableCellNode(firstCell) ? firstCell.getHeaderStyles() !== TableCellHeaderStates.NO_STATUS : false; + const cells = row.getChildren(); + for (const cell of cells) { + if ($isTableCellNode(cell)) { + cell.setHeaderStyles(isHeader ? TableCellHeaderStates.NO_STATUS : TableCellHeaderStates.ROW); + } + } + return !isHeader; +} diff --git a/resources/sass/_tables.scss b/resources/sass/_tables.scss index 16be32fb39b..5ea520d733d 100644 --- a/resources/sass/_tables.scss +++ b/resources/sass/_tables.scss @@ -4,10 +4,6 @@ table { min-width: 100px; max-width: 100%; - thead { - @include mixins.lightDark(background-color, #f8f8f8, #333); - font-weight: 500; - } td, th { min-width: 10px; padding: 6px 8px; @@ -22,6 +18,15 @@ table { } } +// Table Header styles. +// Initial selector for in-body th cells, intended to (somewhat) not conflict with +// previous approach (second selector) to table headers where +// we would target the thead specifically. +table:not(:has(thead)) th, table thead { + @include mixins.lightDark(background-color, #f8f8f8, #333); + font-weight: 500; +} + table.table { width: 100%; tr td, tr th { From 7254dc3ab54f6005a5075d4ce10d2d09fb597727 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 9 May 2026 15:38:44 +0100 Subject: [PATCH 149/204] Lexical: Fixed diagrams not updating on edit Caused by lack of proper key use on clone --- resources/js/wysiwyg/lexical/rich-text/LexicalDiagramNode.ts | 2 +- resources/js/wysiwyg/ui/decorators/DiagramDecorator.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/js/wysiwyg/lexical/rich-text/LexicalDiagramNode.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalDiagramNode.ts index e69f97848ac..6be19c29371 100644 --- a/resources/js/wysiwyg/lexical/rich-text/LexicalDiagramNode.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalDiagramNode.ts @@ -27,7 +27,7 @@ export class DiagramNode extends DecoratorNode { } static clone(node: DiagramNode): DiagramNode { - const newNode = new DiagramNode(node.__drawingId, node.__drawingUrl); + const newNode = new DiagramNode(node.__drawingId, node.__drawingUrl, node.__key); newNode.__id = node.__id; return newNode; } diff --git a/resources/js/wysiwyg/ui/decorators/DiagramDecorator.ts b/resources/js/wysiwyg/ui/decorators/DiagramDecorator.ts index e46dcc312ad..58a4de1dcff 100644 --- a/resources/js/wysiwyg/ui/decorators/DiagramDecorator.ts +++ b/resources/js/wysiwyg/ui/decorators/DiagramDecorator.ts @@ -1,5 +1,4 @@ import {EditorDecorator} from "../framework/decorator"; -import {EditorUiContext} from "../framework/core"; import {BaseSelection, CLICK_COMMAND, COMMAND_PRIORITY_NORMAL} from "lexical"; import {DiagramNode} from "@lexical/rich-text/LexicalDiagramNode"; import {$selectionContainsNode, $selectSingleNode} from "../../utils/selection"; From 1ef9b7d48fb9180dea11d3770e1e1e2ccdc1ab5f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 9 May 2026 16:05:21 +0100 Subject: [PATCH 150/204] Lexical: Added a little testing coverage for DiagramNode --- .../js/wysiwyg/lexical/core/LexicalNode.ts | 6 ++-- .../lexical/core/__tests__/utils/index.ts | 4 +++ .../__tests__/unit/LexicalDiagramNode.test.ts | 33 +++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalDiagramNode.test.ts diff --git a/resources/js/wysiwyg/lexical/core/LexicalNode.ts b/resources/js/wysiwyg/lexical/core/LexicalNode.ts index 95a7234694a..4aba2a22f67 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalNode.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalNode.ts @@ -208,9 +208,9 @@ export class LexicalNode { } /** - * Clones this node, creating a new node with a different key - * and adding it to the EditorState (but not attaching it anywhere!). All nodes must - * implement this method. + * Clones this node, creating a new matching instance. + * Should be created with the existing node key if it exists. + * All nodes must implement this method. * */ static clone(_data: unknown): LexicalNode { diff --git a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts index a30c86fcc71..c18c4b4ca74 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts @@ -39,6 +39,8 @@ import {EditorUiContext} from "../../../../ui/framework/core"; import {EditorUIManager} from "../../../../ui/framework/manager"; import {ImageNode} from "@lexical/rich-text/LexicalImageNode"; import {MediaNode} from "@lexical/rich-text/LexicalMediaNode"; +import {DiagramNode} from "@lexical/rich-text/LexicalDiagramNode"; +import {DiagramDecorator} from "../../../../ui/decorators/DiagramDecorator"; type TestEnv = { readonly container: HTMLDivElement; @@ -489,6 +491,7 @@ export function createTestContext(): EditorUiContext { nodes: [ ImageNode, MediaNode, + DiagramNode, ] }); @@ -509,6 +512,7 @@ export function createTestContext(): EditorUiContext { }; context.manager.setContext(context); + context.manager.registerDecoratorType('diagram', DiagramDecorator); return context; } diff --git a/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalDiagramNode.test.ts b/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalDiagramNode.test.ts new file mode 100644 index 00000000000..e6bb244f49a --- /dev/null +++ b/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalDiagramNode.test.ts @@ -0,0 +1,33 @@ +import {createTestContext} from "lexical/__tests__/utils"; +import {$createDiagramNode, DiagramNode} from "@lexical/rich-text/LexicalDiagramNode"; +import {$getHtmlContent} from "@lexical/clipboard"; +import {getEditorContentAsHtml} from "../../../../utils/actions"; +import {$getRoot} from "lexical"; + + +describe('LexicalDiagramNode', () => { + + test('clone creates new instance with same key', () => { + const {editor} = createTestContext(); + editor.updateAndCommit(() => { + const node = $createDiagramNode('10', 'https://example.com/barry.png'); + const clone = DiagramNode.clone(node); + + expect(node).not.toBe(clone); + expect(node.getKey()).toBe(clone.getKey()); + }); + }); + + test('output HTML format', async () => { + const {editor} = createTestContext(); + editor.updateAndCommit(() => { + const node = $createDiagramNode('10', 'https://example.com/barry.png'); + node.setId('cat-123'); + $getRoot().append(node); + }); + + const html = await getEditorContentAsHtml(editor); + expect(html).toBe(`
    `); + }); + +}); \ No newline at end of file From b53499932bdcfa29b1a6c20aaf9e52f3e49fc03c Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 9 May 2026 17:54:24 +0100 Subject: [PATCH 151/204] Lexical: Fixed actions not applying on empty state Updated editor to always attempt to start from at least a paragraph isntead of empty state. Improved focus on HTML change. --- .../js/wysiwyg/ui/defaults/forms/controls.ts | 6 +- .../wysiwyg/utils/__tests__/actions.test.ts | 107 ++++++++++++++++++ resources/js/wysiwyg/utils/actions.ts | 17 ++- .../pages/parts/wysiwyg-editor.blade.php | 2 +- 4 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 resources/js/wysiwyg/utils/__tests__/actions.test.ts diff --git a/resources/js/wysiwyg/ui/defaults/forms/controls.ts b/resources/js/wysiwyg/ui/defaults/forms/controls.ts index 8e7219d6749..c92a16834ed 100644 --- a/resources/js/wysiwyg/ui/defaults/forms/controls.ts +++ b/resources/js/wysiwyg/ui/defaults/forms/controls.ts @@ -1,12 +1,16 @@ import {EditorFormDefinition} from "../../framework/forms"; import {EditorUiContext, EditorUiElement} from "../../framework/core"; -import {setEditorContentFromHtml} from "../../../utils/actions"; +import {focusEditor, setEditorContentFromHtml} from "../../../utils/actions"; import {ExternalContent} from "../../framework/blocks/external-content"; export const source: EditorFormDefinition = { submitText: 'Save', async action(formData, context: EditorUiContext) { setEditorContentFromHtml(context.editor, formData.get('source')?.toString() || ''); + context.editor.commitUpdates(); + window.requestAnimationFrame(() => { + focusEditor(context.editor); + }); return true; }, fields: [ diff --git a/resources/js/wysiwyg/utils/__tests__/actions.test.ts b/resources/js/wysiwyg/utils/__tests__/actions.test.ts new file mode 100644 index 00000000000..9450638f57f --- /dev/null +++ b/resources/js/wysiwyg/utils/__tests__/actions.test.ts @@ -0,0 +1,107 @@ +import { + createTestContext, + destroyFromContext, + expectNodeShapeToMatch, +} from "lexical/__tests__/utils"; +import { $createParagraphNode, $createTextNode, $getRoot, LexicalEditor } from "lexical"; +import { EditorUiContext } from "../../ui/framework/core"; +import { setEditorContentFromHtml } from "../actions"; + +describe('Actions', () => { + + let context!: EditorUiContext; + let editor!: LexicalEditor; + + beforeEach(() => { + context = createTestContext(); + editor = context.editor; + }); + + afterEach(() => { + destroyFromContext(context); + }); + + describe('setEditorContentFromHtml', () => { + it('parses HTML and sets content in editor', async () => { + setEditorContentFromHtml(editor, '

    Hello World

    '); + await Promise.resolve().then(); + + expectNodeShapeToMatch(editor, [ + { + type: 'paragraph', + children: [{ text: 'Hello World' }], + }, + ]); + }); + + it('parses multiple block elements', async () => { + setEditorContentFromHtml(editor, '

    First

    Second

    '); + await Promise.resolve().then(); + + expectNodeShapeToMatch(editor, [ + { type: 'paragraph', children: [{ text: 'First' }] }, + { type: 'paragraph', children: [{ text: 'Second' }] }, + ]); + }); + + it('wraps plain text in a paragraph', async () => { + setEditorContentFromHtml(editor, 'Plain text'); + await Promise.resolve().then(); + + expectNodeShapeToMatch(editor, [ + { + type: 'paragraph', + children: [{ text: 'Plain text' }], + }, + ]); + }); + + it('ensures at least a paragraph when HTML is empty', async () => { + setEditorContentFromHtml(editor, ''); + await Promise.resolve().then(); + + expectNodeShapeToMatch(editor, [ + { type: 'paragraph' }, + ]); + }); + + it('ensures at least a paragraph when HTML contains only whitespace', async () => { + setEditorContentFromHtml(editor, ' '); + await Promise.resolve().then(); + + expectNodeShapeToMatch(editor, [ + { type: 'paragraph' }, + ]); + }); + + it('clears existing content before setting new content', async () => { + editor.updateAndCommit(() => { + const p = $createParagraphNode(); + p.append($createTextNode('Existing')); + $getRoot().append(p); + }); + + setEditorContentFromHtml(editor, '

    New

    '); + await Promise.resolve().then(); + + expectNodeShapeToMatch(editor, [ + { type: 'paragraph', children: [{ text: 'New' }] }, + ]); + }); + + it('handles nested HTML structures', async () => { + setEditorContentFromHtml(editor, '
    • Item A
    • Item B
    '); + await Promise.resolve().then(); + + expectNodeShapeToMatch(editor, [ + { + type: 'list', + children: [ + { type: 'listitem', children: [{ text: 'Item A' }] }, + { type: 'listitem', children: [{ text: 'Item B' }] }, + ], + }, + ]); + }); + }); +}); diff --git a/resources/js/wysiwyg/utils/actions.ts b/resources/js/wysiwyg/utils/actions.ts index e18ac515f51..465d44d3136 100644 --- a/resources/js/wysiwyg/utils/actions.ts +++ b/resources/js/wysiwyg/utils/actions.ts @@ -1,4 +1,4 @@ -import {$getRoot, $getSelection, $insertNodes, $isBlockElementNode, LexicalEditor} from "lexical"; +import {$createParagraphNode, $getRoot, $getSelection, $insertNodes, $isBlockElementNode, LexicalEditor} from "lexical"; import {$generateHtmlFromNodes} from "@lexical/html"; import {$getNearestNodeBlockParent, $htmlToBlockNodes, $htmlToNodes} from "./nodes"; @@ -12,6 +12,12 @@ export function setEditorContentFromHtml(editor: LexicalEditor, html: string) { const nodes = $htmlToBlockNodes(editor, html); root.append(...nodes); + + // Always ensure we at least have a paragraph in the root + // as a target for the cursor/focus/actions. + if (root.isEmpty()) { + root.append($createParagraphNode()); + } }); } @@ -85,5 +91,14 @@ export function getEditorContentAsHtml(editor: LexicalEditor): Promise { } export function focusEditor(editor: LexicalEditor): void { + editor.update(() => { + const root = $getRoot(); + const selection = $getSelection(); + const firstChild = root.getFirstChild(); + if (firstChild && !selection) { + firstChild.selectStart(); + } + }); + editor.commitUpdates(); editor.focus(() => {}, {defaultSelection: "rootStart"}); } \ No newline at end of file diff --git a/resources/views/pages/parts/wysiwyg-editor.blade.php b/resources/views/pages/parts/wysiwyg-editor.blade.php index 73e6557527f..4344b234d60 100644 --- a/resources/views/pages/parts/wysiwyg-editor.blade.php +++ b/resources/views/pages/parts/wysiwyg-editor.blade.php @@ -11,7 +11,7 @@ class="flex-container-column flex-fill flex"> {{--
    --}} - +
    @if($errors->has('html')) From 5f306801d7a192021a01021aec07a7976514d94e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 9 May 2026 19:17:37 +0100 Subject: [PATCH 152/204] Lexical: Used non-breaking spaces instead of text spans for whitespace Leading/trailing text whitespace would use spans with css whitespace rules so the spaces would be represented, but this would lead to a messier output. Instead, this attempts to smartly use non-breaking spaces, along with normal spaces, to represent white space in a way that still allows breaking. It attempts to reduce non-breaking spaces where not needed (next to other inline content). Also fixes duplicate usage on italic content. --- .../js/wysiwyg/lexical/core/LexicalUtils.ts | 4 +++ .../lexical/core/nodes/LexicalTextNode.ts | 30 ++++++++++++---- .../__tests__/unit/LexicalTextNode.test.ts | 35 ++++++++++++++++--- 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/resources/js/wysiwyg/lexical/core/LexicalUtils.ts b/resources/js/wysiwyg/lexical/core/LexicalUtils.ts index cff86a74a2b..840c5e8abae 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalUtils.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalUtils.ts @@ -1338,6 +1338,10 @@ export function $isInlineElementOrDecoratorNode(node: LexicalNode): boolean { ); } +export function $isInlineElementOrTextNode(node: LexicalNode|null): boolean { + return node !== null && ($isTextNode(node) || ($isElementNode(node) && node.isInline())); +} + export function $getNearestRootOrShadowRoot( node: LexicalNode, ): RootNode | ElementNode { diff --git a/resources/js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts b/resources/js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts index 35cc073a0ba..6588b7e10cc 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts @@ -58,7 +58,7 @@ import { import {errorOnReadOnly} from '../LexicalUpdates'; import { $applyNodeReplacement, - $getCompositionKey, + $getCompositionKey, $isInlineElementOrTextNode, $setCompositionKey, getCachedClassNameArray, internalMarkSiblingsAsDirty, @@ -275,6 +275,16 @@ function wrapElementWith( return el; } +function alternatingWhitespaceReplacer(inlineAdjacent: boolean): (match: string) => string { + return (match: string): string => { + let offset = inlineAdjacent ? 1 : 0; + return match + .split('') + .map((char, i) => (i % 2 === offset ? '\u00A0' : char)) + .join(''); + }; +} + // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export interface TextNode { getTopLevelElement(): ElementNode | null; @@ -626,10 +636,15 @@ export class TextNode extends LexicalNode { 'Expected TextNode createDOM to always return a HTMLElement', ); - // Wrap up to retain space if head/tail whitespace exists - const text = this.getTextContent(); - if (/^\s|\s$/.test(text)) { - element.style.whiteSpace = 'pre-wrap'; + // Handle head/tail whitespace if it exists + let text = this.getTextContent(); + const prevIsInline = $isInlineElementOrTextNode(this.getPreviousSibling()); + const nextIsInline = $isInlineElementOrTextNode(this.getNextSibling()); + if (/^\s/.test(text)) { + text = text.replace(/^(\s+)/, alternatingWhitespaceReplacer(prevIsInline)); + } + if (/\s$/.test(text)) { + text = text.replace(/(\s+)$/, alternatingWhitespaceReplacer(nextIsInline)); } // Strip editor theme classes @@ -642,6 +657,9 @@ export class TextNode extends LexicalNode { element.removeAttribute('class'); } + // Apply whitespace replacement to the element + element.textContent = text; + // Remove placeholder tag if redundant if (element.nodeName === 'SPAN' && !element.getAttribute('style')) { element = document.createTextNode(text); @@ -653,7 +671,7 @@ export class TextNode extends LexicalNode { if (this.hasFormat('bold') && originalElementName !== 'strong') { element = wrapElementWith(element, 'strong'); } - if (this.hasFormat('italic')) { + if (this.hasFormat('italic') && originalElementName !== 'em') { element = wrapElementWith(element, 'em'); } if (this.hasFormat('strikethrough')) { diff --git a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTextNode.test.ts b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTextNode.test.ts index 0dbf9b94ed7..8c81d13ac5b 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTextNode.test.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTextNode.test.ts @@ -42,6 +42,7 @@ import { getEditorStateTextContent, } from '../../../LexicalUtils'; import {$generateHtmlFromNodes} from "@lexical/html"; +import {setEditorContentFromHtml} from "../../../../../utils/actions"; const editorConfig = Object.freeze({ namespace: '', @@ -806,12 +807,13 @@ describe('LexicalTextNode tests', () => { }); }); - test('simple text wrapped in span if leading or ending spacing', async () => { + test('non-breaking-spaces used if leading or ending spacing', async () => { const textByExpectedHtml = { - 'hello ': '

    hello

    ', - ' hello': '

    hello

    ', - ' hello ': '

    hello

    ', + 'hello ': '

    hello 

    ', + ' hello': '

     hello

    ', + ' hello ': '

     hello 

    ', + 'hello ': '

    hello   

    ', } await update(() => { @@ -827,6 +829,31 @@ describe('LexicalTextNode tests', () => { }); }); + test('normal spaces used when text is adjacent to other inline text', async () => { + await update(() => { + setEditorContentFromHtml($getEditor(), '

     Hello there is text here 

    '); + }); + + await update(() => { + const html = $generateHtmlFromNodes($getEditor(), null); + expect(html).toBe('

     Hello there is text here 

    '); + }); + }); + + test('normal and non-breaking spaces used when text with multiple spaces is adjacent to inline text', async () => { + await update(() => { + const paragraph = $getRoot().getFirstChild()!; + $getRoot().append(paragraph); + paragraph.append($createTextNode('hello ')); + const bold = $createTextNode('world '); + bold.setFormat("bold"); + paragraph.append(bold); + + const html = $generateHtmlFromNodes($getEditor(), null); + expect(html).toBe('

    hello   world   

    '); + }); + }); + test('text with formats exports using format elements instead of classes', async () => { await update(() => { const paragraph = $getRoot().getFirstChild()!; From 16a50b0ca9af301482b5d1ad32a5e16d99ec3e7e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 9 May 2026 19:36:43 +0100 Subject: [PATCH 153/204] Lexical: Fixed updating of TextNode text on export --- .../js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/resources/js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts b/resources/js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts index 6588b7e10cc..860f0392525 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts @@ -657,12 +657,17 @@ export class TextNode extends LexicalNode { element.removeAttribute('class'); } - // Apply whitespace replacement to the element - element.textContent = text; - // Remove placeholder tag if redundant if (element.nodeName === 'SPAN' && !element.getAttribute('style')) { element = document.createTextNode(text); + } else { + // Apply whitespace replaced text to the element + // Search down the child chain in the event this element is already wrapped + let child: Element = element; + while (child.childElementCount > 0) { + child = child.children[0]; + } + child.textContent = text; } // This is the only way to properly add support for most clients, From e98244398824fa622db86b72ec8e76f2c3461ccc Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 10 May 2026 12:58:17 +0100 Subject: [PATCH 154/204] Lexical: Made toolbar placement smarter - Set z-index so toolbars for deeper (more specific) content is bought forward above more general toolbars. - Added some basic overlap checking as an indicator to whether the toolbar should sit above the target. --- resources/js/wysiwyg/ui/framework/manager.ts | 8 +- resources/js/wysiwyg/ui/framework/toolbars.ts | 75 +++++++++++++++---- 2 files changed, 68 insertions(+), 15 deletions(-) diff --git a/resources/js/wysiwyg/ui/framework/manager.ts b/resources/js/wysiwyg/ui/framework/manager.ts index f8525bc7c69..4dab455fe9b 100644 --- a/resources/js/wysiwyg/ui/framework/manager.ts +++ b/resources/js/wysiwyg/ui/framework/manager.ts @@ -167,8 +167,10 @@ export class EditorUIManager { triggerLayoutUpdate(): void { window.requestAnimationFrame(() => { + const toolbarBounds: (DOMRect|null)[] = []; for (const toolbar of this.activeContextToolbars) { - toolbar.updatePosition(); + const bounds = toolbar.updatePosition(toolbarBounds); + toolbarBounds.push(bounds); } }); } @@ -247,13 +249,15 @@ export class EditorUIManager { } } + const toolbarBounds: (DOMRect|null)[] = []; for (const [target, contents] of contentByTarget) { const toolbar = new EditorContextToolbar(target, contents); toolbar.setContext(this.getContext()); this.activeContextToolbars.push(toolbar); this.getContext().containerDOM.append(toolbar.getDOMElement()); - toolbar.updatePosition(); + const bounds = toolbar.updatePosition(toolbarBounds); + toolbarBounds.push(bounds); } } diff --git a/resources/js/wysiwyg/ui/framework/toolbars.ts b/resources/js/wysiwyg/ui/framework/toolbars.ts index cf5ec4ad151..bd86eca7e92 100644 --- a/resources/js/wysiwyg/ui/framework/toolbars.ts +++ b/resources/js/wysiwyg/ui/framework/toolbars.ts @@ -23,8 +23,14 @@ export class EditorContextToolbar extends EditorContainerUiElement { }, this.getChildren().map(child => child.getDOMElement())); } - updatePosition() { - const editorBounds = this.getContext().scrollDOM.getBoundingClientRect(); + /** + * Update the position of the toolbar based on the target element. + * Takes the bounds of other toolbars processed so far so that they can be considered + * when positioning the toolbar to help prevent overlaps. + */ + updatePosition(otherBounds: (DOMRect|null)[] = []): DOMRect|null { + const context = this.getContext(); + const editorBounds = context.scrollDOM.getBoundingClientRect(); const targetBounds = this.target.getBoundingClientRect(); const dom = this.getDOMElement(); const domBounds = dom.getBoundingClientRect(); @@ -36,23 +42,43 @@ export class EditorContextToolbar extends EditorContainerUiElement { if (!this.target.isConnected) { // If our target is no longer in the DOM, tell the manager an update is needed. - this.getContext().manager.triggerFutureStateRefresh(); - return; + context.manager.triggerFutureStateRefresh(); + return null; } else if (!showing) { - return; + return null; } - const showAbove: boolean = targetBounds.bottom + 6 + domBounds.height > editorBounds.bottom; - dom.classList.toggle('is-above', showAbove); - const targetMid = targetBounds.left + (targetBounds.width / 2); - const targetLeft = targetMid - (domBounds.width / 2); + const intendedBounds: DOMRectInit = { + x: targetMid -(domBounds.width / 2), + y: targetBounds.bottom + 6, + width: domBounds.width, + height: domBounds.height, + }; + + let showAbove: boolean = ( + targetBounds.bottom + 6 + domBounds.height > editorBounds.bottom + || this.willOverlapWithOthersIfBelow(intendedBounds, otherBounds) + ); + dom.classList.toggle('is-above', showAbove); if (showAbove) { - dom.style.top = (targetBounds.top - 6 - domBounds.height) + 'px'; - } else { - dom.style.top = (targetBounds.bottom + 6) + 'px'; + intendedBounds.y = targetBounds.top - 6 - domBounds.height; + } + + dom.style.top = intendedBounds.y + 'px'; + dom.style.left = intendedBounds.x + 'px'; + + // Set z-index based on depth, so that the most specific toolbar + // is bought forward. + let depth = 1; + let parent = this.target.parentElement; + while (parent && parent !== context.editorDOM) { + parent = parent.parentElement; + depth++; } - dom.style.left = targetLeft + 'px'; + dom.style.zIndex = `${depth}`; + + return dom.getBoundingClientRect(); } insert(children: EditorUiElement[]) { @@ -60,4 +86,27 @@ export class EditorContextToolbar extends EditorContainerUiElement { const dom = this.getDOMElement(); dom.append(...children.map(child => child.getDOMElement())); } + + protected willOverlapWithOthersIfBelow(intendedBounds: DOMRectInit, otherBounds: (DOMRect|null)[]): boolean { + for (const bounds of otherBounds) { + if (bounds === null) continue; + + const iLeft = intendedBounds.x ?? 0; + const iTop = intendedBounds.y ?? 0; + const iRight = iLeft + (intendedBounds.width ?? 0); + const iBottom = iTop + (intendedBounds.height ?? 0); + + const overlaps = ( + iLeft < bounds.right && + iRight > bounds.left && + iTop < bounds.bottom && + iBottom > bounds.top + ); + + if (overlaps) { + return true; + } + } + return false; + } } \ No newline at end of file From 6367f007c18b7e7bfac61fd077c9b777aa03a7ee Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 10 May 2026 13:04:39 +0100 Subject: [PATCH 155/204] Lexical: Added fade to table resizers --- resources/js/wysiwyg/ui/framework/toolbars.ts | 4 ++-- resources/sass/_editor.scss | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/js/wysiwyg/ui/framework/toolbars.ts b/resources/js/wysiwyg/ui/framework/toolbars.ts index bd86eca7e92..95f0af11c50 100644 --- a/resources/js/wysiwyg/ui/framework/toolbars.ts +++ b/resources/js/wysiwyg/ui/framework/toolbars.ts @@ -50,7 +50,7 @@ export class EditorContextToolbar extends EditorContainerUiElement { const targetMid = targetBounds.left + (targetBounds.width / 2); const intendedBounds: DOMRectInit = { - x: targetMid -(domBounds.width / 2), + x: targetMid - (domBounds.width / 2), y: targetBounds.bottom + 6, width: domBounds.width, height: domBounds.height, @@ -109,4 +109,4 @@ export class EditorContextToolbar extends EditorContainerUiElement { } return false; } -} \ No newline at end of file +} diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss index 0580d7377c9..4c05de0a8fb 100644 --- a/resources/sass/_editor.scss +++ b/resources/sass/_editor.scss @@ -461,6 +461,7 @@ body.editor-is-fullscreen { z-index: 3; user-select: none; opacity: 0; + transition: opacity 120ms ease-in-out; &:hover, &.active { opacity: 0.4; } From 2aba39b176a9350750c0cfced812f479339961bd Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 10 May 2026 17:28:21 +0100 Subject: [PATCH 156/204] Lexical: Updated toolbars to re-focus on editor on escape press --- resources/js/wysiwyg/ui/framework/core.ts | 2 +- resources/js/wysiwyg/ui/framework/toolbars.ts | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/resources/js/wysiwyg/ui/framework/core.ts b/resources/js/wysiwyg/ui/framework/core.ts index 9c524dff057..68a80d2e100 100644 --- a/resources/js/wysiwyg/ui/framework/core.ts +++ b/resources/js/wysiwyg/ui/framework/core.ts @@ -30,7 +30,7 @@ export function isUiBuilderDefinition(object: any): object is EditorUiBuilderDef export abstract class EditorUiElement { protected dom: HTMLElement|null = null; private context: EditorUiContext|null = null; - private abortController: AbortController = new AbortController(); + protected abortController: AbortController = new AbortController(); protected abstract buildDOM(): HTMLElement; diff --git a/resources/js/wysiwyg/ui/framework/toolbars.ts b/resources/js/wysiwyg/ui/framework/toolbars.ts index 95f0af11c50..ec8e9772b04 100644 --- a/resources/js/wysiwyg/ui/framework/toolbars.ts +++ b/resources/js/wysiwyg/ui/framework/toolbars.ts @@ -18,9 +18,20 @@ export class EditorContextToolbar extends EditorContainerUiElement { } protected buildDOM(): HTMLElement { - return el('div', { + const toolbar = el('div', { class: 'editor-context-toolbar', }, this.getChildren().map(child => child.getDOMElement())); + + // Focus back on the editor on escape press + toolbar.addEventListener('keydown', (event: KeyboardEvent) => { + if (event.key === 'Escape') { + event.preventDefault(); + event.stopPropagation(); + this.getContext().editor.focus(); + } + }, {signal: this.abortController.signal}); + + return toolbar; } /** From 5d429ea9bb5d48b4a4b3bab4b09fe21dea00b752 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 11 May 2026 17:07:50 +0100 Subject: [PATCH 157/204] Lexical: Added better support for block content in list items Improved the ability to set/use block formats in lists. Setting a format, will now attempt to create that, if just plain text to start with, when in a list. Added handling so that blocks split into new list elements instead of new blocks within the list element. Also fixed some issues with cyclic import references, and updated editor event types to always include their type for easier debug. --- resources/js/wysiwyg/index.ts | 3 ++ .../wysiwyg/lexical/core/LexicalCommands.ts | 2 +- .../wysiwyg/lexical/core/LexicalSelection.ts | 7 ++- .../lexical/core/nodes/LexicalElementNode.ts | 4 +- .../lexical/list/LexicalListItemNode.ts | 4 +- .../wysiwyg/lexical/list/LexicalListNode.ts | 2 +- .../js/wysiwyg/lexical/list/formatList.ts | 8 +-- resources/js/wysiwyg/lexical/list/index.ts | 38 +++++++++++++- resources/js/wysiwyg/lexical/list/utils.ts | 6 ++- resources/js/wysiwyg/utils/formats.ts | 49 +++++++++++-------- resources/js/wysiwyg/utils/selection.ts | 25 ++++++++++ 11 files changed, 114 insertions(+), 34 deletions(-) diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index dc0ea211f59..59fb2acdea7 100644 --- a/resources/js/wysiwyg/index.ts +++ b/resources/js/wysiwyg/index.ts @@ -29,6 +29,7 @@ import {registerSelectionHandling} from "./services/selection-handling"; import {EditorApi} from "./api/api"; import {registerMentions} from "./services/mentions"; import {MentionDecorator} from "./ui/decorators/MentionDecorator"; +import {registerLists} from "@lexical/list"; const theme = { text: { @@ -58,6 +59,7 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st mergeRegister( registerRichText(editor), + registerLists(editor), registerHistory(editor, createEmptyHistoryState(), 300), registerShortcuts(context, true), registerKeyboardHandling(context), @@ -122,6 +124,7 @@ export function createBasicEditorInstance(container: HTMLElement, htmlContent: s const editorTeardown = mergeRegister( registerRichText(editor), + registerLists(editor), registerHistory(editor, createEmptyHistoryState(), 300), registerShortcuts(context, false), registerAutoLinks(editor), diff --git a/resources/js/wysiwyg/lexical/core/LexicalCommands.ts b/resources/js/wysiwyg/lexical/core/LexicalCommands.ts index 1b378b4a010..36badba977b 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalCommands.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalCommands.ts @@ -16,7 +16,7 @@ import type { export type PasteCommandType = ClipboardEvent | InputEvent | KeyboardEvent; export function createCommand(type?: string): LexicalCommand { - return __DEV__ ? {type} : {}; + return {type}; } export const SELECTION_CHANGE_COMMAND: LexicalCommand = createCommand( diff --git a/resources/js/wysiwyg/lexical/core/LexicalSelection.ts b/resources/js/wysiwyg/lexical/core/LexicalSelection.ts index 36e2db5470a..2904867f072 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalSelection.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalSelection.ts @@ -63,7 +63,6 @@ import { toggleTextFormatType, } from './LexicalUtils'; import {$createTabNode, $isTabNode} from './nodes/LexicalTabNode'; -import {$selectSingleNode} from "../../utils/selection"; export type TextPointType = { _selection: BaseSelection; @@ -2224,6 +2223,12 @@ export function $createNodeSelection(): NodeSelection { return new NodeSelection(new Set()); } +function $selectSingleNode(node: LexicalNode): void { + const nodeSelection = $createNodeSelection(); + nodeSelection.add(node.getKey()); + $setSelection(nodeSelection); +} + export function $internalCreateSelection( editor: LexicalEditor, ): null | BaseSelection { diff --git a/resources/js/wysiwyg/lexical/core/nodes/LexicalElementNode.ts b/resources/js/wysiwyg/lexical/core/nodes/LexicalElementNode.ts index 0329aed6f4e..0304fe4ad37 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/LexicalElementNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/LexicalElementNode.ts @@ -16,11 +16,9 @@ import type {KlassConstructor, Spread} from 'lexical'; import invariant from 'lexical/shared/invariant'; -import {$isTextNode, TextNode} from '../index'; +import {$isTextNode, TextNode} from './LexicalTextNode'; import { DOUBLE_LINE_BREAK, - - } from '../LexicalConstants'; import {LexicalNode} from '../LexicalNode'; import { diff --git a/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts b/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts index 239c49a8c30..a7b04d47617 100644 --- a/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts +++ b/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts @@ -6,7 +6,7 @@ * */ -import type {ListNode, ListType} from './'; +import type {ListNode, ListType} from './LexicalListNode'; import type { BaseSelection, DOMConversionMap, @@ -32,7 +32,7 @@ import { } from 'lexical'; import invariant from 'lexical/shared/invariant'; -import {$createListNode, $isListNode} from './'; +import {$createListNode, $isListNode} from './LexicalListNode'; import {mergeLists} from './formatList'; import {isNestedListNode} from './utils'; import {el} from "../../utils/dom"; diff --git a/resources/js/wysiwyg/lexical/list/LexicalListNode.ts b/resources/js/wysiwyg/lexical/list/LexicalListNode.ts index b5c83adddce..0311e8b313b 100644 --- a/resources/js/wysiwyg/lexical/list/LexicalListNode.ts +++ b/resources/js/wysiwyg/lexical/list/LexicalListNode.ts @@ -30,7 +30,7 @@ import { import invariant from 'lexical/shared/invariant'; import normalizeClassNames from 'lexical/shared/normalizeClassNames'; -import {$createListItemNode, $isListItemNode, ListItemNode} from '.'; +import {$createListItemNode, $isListItemNode, ListItemNode} from './LexicalListItemNode'; import { mergeNextSiblingListIfSameType, updateChildrenListItemValue, diff --git a/resources/js/wysiwyg/lexical/list/formatList.ts b/resources/js/wysiwyg/lexical/list/formatList.ts index aa0d5d61129..5c7b81b7a5f 100644 --- a/resources/js/wysiwyg/lexical/list/formatList.ts +++ b/resources/js/wysiwyg/lexical/list/formatList.ts @@ -25,12 +25,14 @@ import invariant from 'lexical/shared/invariant'; import { $createListItemNode, - $createListNode, $isListItemNode, - $isListNode, ListItemNode, +} from './LexicalListItemNode'; +import { + $createListNode, + $isListNode, ListNode, -} from './'; +} from './LexicalListNode'; import {ListType} from './LexicalListNode'; import { $getAllListItems, diff --git a/resources/js/wysiwyg/lexical/list/index.ts b/resources/js/wysiwyg/lexical/list/index.ts index 157fe79de1d..00e33c115e8 100644 --- a/resources/js/wysiwyg/lexical/list/index.ts +++ b/resources/js/wysiwyg/lexical/list/index.ts @@ -8,7 +8,10 @@ import type {SerializedListItemNode} from './LexicalListItemNode'; import type {ListType, SerializedListNode} from './LexicalListNode'; -import type {LexicalCommand} from 'lexical'; +import { + $getSelection, + $isRangeSelection, COMMAND_PRIORITY_NORMAL, INSERT_PARAGRAPH_COMMAND, LexicalCommand, LexicalEditor +} from 'lexical'; import {createCommand} from 'lexical'; @@ -20,6 +23,8 @@ import { } from './LexicalListItemNode'; import {$createListNode, $isListNode, ListNode} from './LexicalListNode'; import {$getListDepth} from './utils'; +import {mergeRegister} from "@lexical/utils"; +import {$getAncestor, INTERNAL_$isBlock} from "lexical/LexicalUtils"; export { $createListItemNode, @@ -48,3 +53,34 @@ export const INSERT_CHECK_LIST_COMMAND: LexicalCommand = createCommand( export const REMOVE_LIST_COMMAND: LexicalCommand = createCommand( 'REMOVE_LIST_COMMAND', ); + +export function registerLists(editor: LexicalEditor): () => void { + return mergeRegister( + + // Override the default insert paragraph command when within a list item + // so that new blocks are inserted as their own list items. + editor.registerCommand(INSERT_PARAGRAPH_COMMAND, () => { + const selection = $getSelection(); + if (!$isRangeSelection(selection)) { + return false; + } + + const anchorNode = selection.anchor.getNode(); + const block = $getAncestor(anchorNode, INTERNAL_$isBlock)!; + const blockParent = block.getParent(); + if ($isListItemNode(blockParent)) { + const newBlock = selection.insertParagraph(); + if (newBlock) { + const newListItem = $createListItemNode(); + const newBlockSiblings = newBlock.getNextSiblings(); + newListItem.append(newBlock, ...newBlockSiblings); + blockParent.insertAfter(newListItem, false); + newListItem.selectStart(); + } + return true; + } + + return false; + }, COMMAND_PRIORITY_NORMAL), + ); +} diff --git a/resources/js/wysiwyg/lexical/list/utils.ts b/resources/js/wysiwyg/lexical/list/utils.ts index c451a45085d..984e7701675 100644 --- a/resources/js/wysiwyg/lexical/list/utils.ts +++ b/resources/js/wysiwyg/lexical/list/utils.ts @@ -14,10 +14,12 @@ import invariant from 'lexical/shared/invariant'; import { $createListItemNode, $isListItemNode, - $isListNode, ListItemNode, +} from './LexicalListItemNode'; +import { + $isListNode, ListNode, -} from './'; +} from './LexicalListNode'; /** * Checks the depth of listNode from the root node. diff --git a/resources/js/wysiwyg/utils/formats.ts b/resources/js/wysiwyg/utils/formats.ts index a5f06f147d2..fbc11af490a 100644 --- a/resources/js/wysiwyg/utils/formats.ts +++ b/resources/js/wysiwyg/utils/formats.ts @@ -3,7 +3,7 @@ import { $createTextNode, $getSelection, $insertNodes, - $isParagraphNode, + $isParagraphNode, $isRangeSelection, LexicalEditor, LexicalNode } from "lexical"; @@ -16,10 +16,12 @@ import { } from "./selection"; import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "@lexical/rich-text/LexicalCodeBlockNode"; import {$createCalloutNode, $isCalloutNode, CalloutCategory} from "@lexical/rich-text/LexicalCalloutNode"; -import {$isListNode, insertList, ListNode, ListType, removeList} from "@lexical/list"; +import {$isListItemNode, $isListNode, insertList, ListNode, ListType, removeList} from "@lexical/list"; import {$createLinkNode, $isLinkNode} from "@lexical/link"; import {$createHeadingNode, $isHeadingNode, HeadingTagType} from "@lexical/rich-text/LexicalHeadingNode"; import {$createQuoteNode, $isQuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; +import {$setBlocksType} from "@lexical/selection"; + const $isHeaderNodeOfTag = (node: LexicalNode | null | undefined, tag: HeadingTagType) => { return $isHeadingNode(node) && node.getTag() === tag; @@ -62,28 +64,35 @@ export function toggleSelectionAsList(editor: LexicalEditor, type: ListType) { } export function formatCodeBlock(editor: LexicalEditor) { - editor.getEditorState().read(() => { + editor.update(() => { const selection = $getSelection(); const lastSelection = getLastSelection(editor); const codeBlock = $getNodeFromSelection(lastSelection, $isCodeBlockNode) as (CodeBlockNode | null); if (codeBlock === null) { - editor.update(() => { - const codeBlock = $createCodeBlockNode(); - codeBlock.setCode(selection?.getTextContent() || ''); - - const selectionNodes = $getBlockElementNodesInSelection(selection); - const firstSelectionNode = selectionNodes[0]; - const extraNodes = selectionNodes.slice(1); - if (firstSelectionNode) { - firstSelectionNode.replace(codeBlock); - extraNodes.forEach(n => n.remove()); - } else { - $insertNewBlockNodeAtSelection(codeBlock, true); - } - - $openCodeEditorForNode(editor, codeBlock); - $selectSingleNode(codeBlock); - }); + const codeBlock = $createCodeBlockNode(); + + const codeLines = []; + const selectionNodes = $getBlockElementNodesInSelection(selection); + for (const node of selectionNodes) { + codeLines.push(node.getTextContent()); + } + codeBlock.setCode(codeLines.join('\n')); + + const firstSelectionNode = selectionNodes[0]; + const extraNodes = selectionNodes.slice(1); + if ($isListItemNode(firstSelectionNode)) { + firstSelectionNode.getChildren().forEach(c => c.remove()); + firstSelectionNode.append(codeBlock); + extraNodes.forEach(n => n.remove()); + } else if (firstSelectionNode) { + firstSelectionNode.replace(codeBlock); + extraNodes.forEach(n => n.remove()); + } else { + $insertNewBlockNodeAtSelection(codeBlock, true); + } + + $openCodeEditorForNode(editor, codeBlock); + $selectSingleNode(codeBlock); } else { $openCodeEditorForNode(editor, codeBlock); } diff --git a/resources/js/wysiwyg/utils/selection.ts b/resources/js/wysiwyg/utils/selection.ts index 28050571ede..e6815e25022 100644 --- a/resources/js/wysiwyg/utils/selection.ts +++ b/resources/js/wysiwyg/utils/selection.ts @@ -17,6 +17,7 @@ import {$setBlocksType} from "@lexical/selection"; import {$getNearestNodeBlockParent, $getParentOfType, nodeHasAlignment} from "./nodes"; import {CommonBlockAlignment} from "lexical/nodes/common"; +import {$isListItemNode} from "@lexical/list"; const lastSelectionByEditor = new WeakMap; @@ -76,9 +77,33 @@ export function $selectionContainsTextFormat(selection: BaseSelection | null, fo return false; } +function createNewBlockIfSelectionIsSingleListItemText(selection: BaseSelection): void { + const startEnd = selection.getStartEndPoints(); + if (!startEnd) { + return; + } + + const startBlock = $getNearestNodeBlockParent(startEnd[0].getNode()); + const endBlock = $getNearestNodeBlockParent(startEnd[1].getNode()); + const isSingleListItemTextSelection = $isListItemNode(startBlock) && startBlock.getKey() === endBlock?.getKey(); + + if (isSingleListItemTextSelection) { + const wrapper = $createParagraphNode(); + const startNode = startEnd[0].getNode(); + startNode.insertBefore(wrapper); + wrapper.append(...selection.getNodes()); + } +} + export function $toggleSelectionBlockNodeType(matcher: LexicalNodeMatcher, creator: LexicalElementNodeCreator) { const selection = $getSelection(); const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null; + + const inListItem = $isListItemNode(blockElement); + if (inListItem && selection) { + createNewBlockIfSelectionIsSingleListItemText(selection); + } + if (selection && matcher(blockElement)) { $setBlocksType(selection, $createParagraphNode); } else { From 9f4afac7bc44f9692551e832b1065c606b09f795 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 12 May 2026 18:42:06 +0100 Subject: [PATCH 158/204] Lexical: Improved ability to break out of lists Updates list handling so that you can break out of a list (or move down a level) via enter on an existing empty list item at any point in the list, not just the end. Added test to cover. --- .../lexical/list/LexicalListItemNode.ts | 23 +++---- .../unit/LexicalListItemNode.test.ts | 68 ++++++++++++++++++- resources/js/wysiwyg/utils/lists.ts | 33 ++++++++- 3 files changed, 104 insertions(+), 20 deletions(-) diff --git a/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts b/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts index a7b04d47617..2a1b698b123 100644 --- a/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts +++ b/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts @@ -36,6 +36,7 @@ import {$createListNode, $isListNode} from './LexicalListNode'; import {mergeLists} from './formatList'; import {isNestedListNode} from './utils'; import {el} from "../../utils/dom"; +import {$escapeListAtItem} from "../../utils/lists"; export type SerializedListItemNode = Spread< { @@ -273,21 +274,13 @@ export class ListItemNode extends ElementNode { restoreSelection = true, ): ListItemNode | ParagraphNode | null { - if (this.getTextContent().trim() === '' && this.isLastChild()) { - const list = this.getParentOrThrow(); - const parentListItem = list.getParent(); - if ($isListItemNode(parentListItem)) { - // Un-nest list item if empty nested item - parentListItem.insertAfter(this); - this.selectStart(); - return null; - } else { - // Insert empty paragraph after list if adding after last empty child - const paragraph = $createParagraphNode(); - list.insertAfter(paragraph, restoreSelection); - this.remove(); - return paragraph; - } + // If we're adding a new empty item, and coming from an empty item, + // take that as a desire to break from the current list level. + // Intended to be a bit more lenient on the last list item hence ignores whitespace, + // which allows empty items to be listed with just a space (except for last). + const textContent = this.getTextContent(); + if (textContent === '' || (textContent.trim() === '' && this.isLastChild())) { + return $escapeListAtItem(this); } const newElement = $createListItemNode( diff --git a/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts b/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts index 10ff0fc6699..48fa2faf8b0 100644 --- a/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts +++ b/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts @@ -9,12 +9,12 @@ import { $createParagraphNode, $createRangeSelection, - $getRoot, LexicalEditor, + $getRoot, LexicalEditor, LexicalNode, ParagraphNode, TextNode, } from 'lexical'; import { createTestContext, destroyFromContext, - expectHtmlToBeEqual, + expectHtmlToBeEqual, expectNodeShapeToMatch, html, } from 'lexical/__tests__/utils'; @@ -1200,6 +1200,70 @@ describe('LexicalListItemNode tests', () => { `, ); }); + + test('new items after empty top-level items splits the list in two, and inserts a paragraph inbetween', () => { + let newItem: LexicalNode|null = null; + const input = `
      +
    • Item A
    • +
    • +
    • Item C
    • +
    `; + + editor.updateAndCommit(() => { + const root = $getRoot(); + root.append(...$htmlToBlockNodes(editor, input)); + const list = root.getFirstChild() as ListNode; + const itemB = list.getChildAtIndex(1) as ListItemNode; + + newItem = itemB.insertNewAfter($createRangeSelection()); + }); + + expect(newItem).toBeInstanceOf(ParagraphNode); + + expectNodeShapeToMatch(editor, [ + { + type: 'list', + children: [{type: 'listitem', children: [{text: 'Item A'}]}], + }, + { + type: 'paragraph', + }, + { + type: 'list', + children: [{type: 'listitem', children: [{text: 'Item C'}]}], + } + ]) + }); + + test('new items after last empty top-level inserts a new paragraph below, and removes the list item', () => { + let newItem: LexicalNode|null = null; + const input = `
      +
    • Item A
    • +
    • Item B
    • +
    • +
    `; + + editor.updateAndCommit(() => { + const root = $getRoot(); + root.append(...$htmlToBlockNodes(editor, input)); + const list = root.getFirstChild() as ListNode; + const itemC = list.getChildAtIndex(2) as ListItemNode; + + newItem = itemC.insertNewAfter($createRangeSelection()); + }); + + expect(newItem).toBeInstanceOf(ParagraphNode); + + expectNodeShapeToMatch(editor, [ + { + type: 'list', + children: [{type: 'listitem', children: [{text: 'Item A'}]}, {type: 'listitem', children: [{text: 'Item B'}]}], + }, + { + type: 'paragraph', + }, + ]) + }); }); test('$createListItemNode()', async () => { diff --git a/resources/js/wysiwyg/utils/lists.ts b/resources/js/wysiwyg/utils/lists.ts index 3deb9dfb6e9..cdbaba6fbbf 100644 --- a/resources/js/wysiwyg/utils/lists.ts +++ b/resources/js/wysiwyg/utils/lists.ts @@ -1,7 +1,7 @@ -import {$createTextNode, $getSelection, BaseSelection, LexicalEditor, TextNode} from "lexical"; +import {$createParagraphNode, $createTextNode, $getSelection, BaseSelection, LexicalEditor, ParagraphNode, TextNode} from "lexical"; import {$getBlockElementNodesInSelection, $selectNodes, $toggleSelection} from "./selection"; import {$sortNodes, nodeHasInset} from "./nodes"; -import {$createListItemNode, $createListNode, $isListItemNode, $isListNode, ListItemNode} from "@lexical/list"; +import {$createListItemNode, $createListNode, $isListItemNode, $isListNode, ListItemNode, ListNode} from "@lexical/list"; export function $nestListItem(node: ListItemNode): ListItemNode { @@ -186,4 +186,31 @@ export function $setInsetForSelection(editor: LexicalEditor, change: number): vo } $toggleSelection(editor); -} \ No newline at end of file +} + +export function $escapeListAtItem(item: ListItemNode): ParagraphNode|null { + const list = item.getParentOrThrow(); + const parentListItem = list.getParent(); + + // Un-nest list item if it's an empty nested item + if ($isListItemNode(parentListItem)) { + parentListItem.insertAfter(item); + item.selectStart(); + return null; + } + + // If we're anywhere but at the end, split the list, with following items + // moved into their own new list. + if (!item.isLastChild()) { + const afterSiblings = item.getNextSiblings(); + const newList = $createListNode(list.getListType()); + newList.append(...afterSiblings); + list.insertAfter(newList); + } + + // Insert a new empty paragraph to land on after our list + const paragraph = $createParagraphNode(); + list.insertAfter(paragraph, true); + item.remove(); + return paragraph; +} From ddb0a22504bedb121a3cbe0b7d3233704f0f2903 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 13 May 2026 16:54:45 +0100 Subject: [PATCH 159/204] Lexical: Made table cell up/down arrow nav smarter Added logic to attempt to retain x position when navigating cells up/down via arrow keys. --- .../wysiwyg/lexical/core/LexicalSelection.ts | 7 ++++ .../lexical/table/LexicalCaptionNode.ts | 1 - .../table/LexicalTableSelectionHelpers.ts | 15 ++++++- resources/js/wysiwyg/utils/selection.ts | 42 +++++++++++++++++-- 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/resources/js/wysiwyg/lexical/core/LexicalSelection.ts b/resources/js/wysiwyg/lexical/core/LexicalSelection.ts index 2904867f072..73ce0edbb11 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalSelection.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalSelection.ts @@ -2219,6 +2219,13 @@ export function $createRangeSelection(): RangeSelection { return new RangeSelection(anchor, focus, 0, ''); } +export function $createCollapsedRangeSelectionForNode(node: LexicalNode, offset: number = 0): RangeSelection { + const type = $isTextNode(node) ? 'text' : 'element'; + const anchor = $createPoint(node.getKey(), offset, type); + const focus = $createPoint(node.getKey(), offset, type); + return new RangeSelection(anchor, focus, 0, ''); +} + export function $createNodeSelection(): NodeSelection { return new NodeSelection(new Set()); } diff --git a/resources/js/wysiwyg/lexical/table/LexicalCaptionNode.ts b/resources/js/wysiwyg/lexical/table/LexicalCaptionNode.ts index d9d83562c29..5f2ef9a25bd 100644 --- a/resources/js/wysiwyg/lexical/table/LexicalCaptionNode.ts +++ b/resources/js/wysiwyg/lexical/table/LexicalCaptionNode.ts @@ -1,7 +1,6 @@ import { $createTextNode, DOMConversionMap, - DOMExportOutput, EditorConfig, ElementNode, LexicalEditor, diff --git a/resources/js/wysiwyg/lexical/table/LexicalTableSelectionHelpers.ts b/resources/js/wysiwyg/lexical/table/LexicalTableSelectionHelpers.ts index 6e5e5416fa0..7e2d93ad9e3 100644 --- a/resources/js/wysiwyg/lexical/table/LexicalTableSelectionHelpers.ts +++ b/resources/js/wysiwyg/lexical/table/LexicalTableSelectionHelpers.ts @@ -72,6 +72,7 @@ import {$isTableRowNode} from './LexicalTableRowNode'; import {$isTableSelection} from './LexicalTableSelection'; import {$computeTableMap, $getNodeTriplet} from './LexicalTableUtils'; import {$selectOrCreateAdjacent} from "../../utils/nodes"; +import {$selectNodeAtXPixelOffset} from "../../utils/selection"; const LEXICAL_ELEMENT_KEY = '__lexicalTableSelection'; @@ -1073,6 +1074,7 @@ const selectTableNodeInDirection = ( x: number, y: number, direction: Direction, + selectionOffset: number = -1 ): boolean => { const isForward = direction === 'forward'; @@ -1112,6 +1114,7 @@ const selectTableNodeInDirection = ( selectTableCellNode( tableNode.getCellNodeFromCordsOrThrow(x, y - 1, tableObserver.table), false, + selectionOffset, ); } else { $selectOrCreateAdjacent(tableNode, false); @@ -1124,6 +1127,7 @@ const selectTableNodeInDirection = ( selectTableCellNode( tableNode.getCellNodeFromCordsOrThrow(x, y + 1, tableObserver.table), true, + selectionOffset, ); } else { $selectOrCreateAdjacent(tableNode, true); @@ -1197,7 +1201,14 @@ function $isSelectionInTable( return false; } -function selectTableCellNode(tableCell: TableCellNode, fromStart: boolean) { +function selectTableCellNode(tableCell: TableCellNode, fromStart: boolean, selectionOffsetPixels : number = -1) { + if (selectionOffsetPixels !== -1) { + const selection = $selectNodeAtXPixelOffset(tableCell, selectionOffsetPixels, fromStart); + if (selection) { + return; + } + } + if (fromStart) { tableCell.selectStart(); } else { @@ -1491,12 +1502,14 @@ function $handleArrowKey( tableObserver.setAnchorCellForSelection(cell); tableObserver.setFocusCellForSelection(cell, true); } else { + const selectionOffset = edgeSelectionRect.x - edgeRect.x; return selectTableNodeInDirection( tableObserver, tableNode, cords.x, cords.y, direction, + selectionOffset ); } diff --git a/resources/js/wysiwyg/utils/selection.ts b/resources/js/wysiwyg/utils/selection.ts index e6815e25022..77aa902f060 100644 --- a/resources/js/wysiwyg/utils/selection.ts +++ b/resources/js/wysiwyg/utils/selection.ts @@ -1,6 +1,6 @@ import { $createNodeSelection, - $createParagraphNode, $createRangeSelection, + $createParagraphNode, $createRangeSelection, $getEditor, $getNearestNodeFromDOMNode, $getRoot, $getSelection, $isBlockElementNode, $isDecoratorNode, $isElementNode, $isParagraphNode, @@ -8,7 +8,7 @@ import { $setSelection, BaseSelection, DecoratorNode, ElementNode, LexicalEditor, - LexicalNode, + LexicalNode, RangeSelection, TextFormatType, TextNode } from "lexical"; import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils"; @@ -18,6 +18,7 @@ import {$setBlocksType} from "@lexical/selection"; import {$getNearestNodeBlockParent, $getParentOfType, nodeHasAlignment} from "./nodes"; import {CommonBlockAlignment} from "lexical/nodes/common"; import {$isListItemNode} from "@lexical/list"; +import {$createCollapsedRangeSelectionForNode} from "lexical/LexicalSelection"; const lastSelectionByEditor = new WeakMap; @@ -300,4 +301,39 @@ export function $getDecoratorNodesInSelection(selection: BaseSelection | null): } return selection.getNodes().filter(node => $isDecoratorNode(node)); -} \ No newline at end of file +} + +/** + * Attempt to select the given node at roughly the pixel offset, relative to the left of the node. + * Returns the range selection if a selection could be made. + * Returns null if no selection can be made. + */ +export function $selectNodeAtXPixelOffset(node: LexicalNode, pixelOffset: number, targetStart: boolean = true): RangeSelection|null { + const targetDOM = $getEditor().getElementByKey(node.getKey()); + if (!targetDOM) { + return null; + } + + const targetChild = targetDOM.children[targetStart ? 0 : targetDOM.children.length - 1] || targetDOM; + const targetBounds = targetChild.getBoundingClientRect(); + const targetY = targetBounds[targetStart ? 'top' : 'bottom'] + (targetStart ? 1 : -1); + const targetX = targetBounds.x + pixelOffset; + // Temporary caretRangeFromPoint usage due to caretPositionFromPoint being only + // very recently supported in Safari + // To remove post 2026 + const caretRange = document.caretRangeFromPoint?.(targetX, targetY); + const caret = document.caretPositionFromPoint?.(targetX, targetY) + ?? (caretRange ? { offsetNode: caretRange.startContainer, offset: caretRange.startOffset } : undefined); + if (!caret) { + return null; + } + + const targetNode = $getNearestNodeFromDOMNode(caret.offsetNode); + if (!targetNode) { + return null; + } + + const rangeSelection = $createCollapsedRangeSelectionForNode(targetNode, caret.offset); + $setSelection(rangeSelection); + return rangeSelection; +} From c87abbd23f2dca8c733a8e010b42a089041fa2ae Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 14 May 2026 18:10:46 +0100 Subject: [PATCH 160/204] Images: Increased validation against related page on upload Adds validation that the related page exists, is visible, and that the user has page edit permissions for the page. Aligns with attachment permission model, and aligns across different image endpoints. Added tests to cover. Closes #6126 --- app/Config/database.php | 1 - .../Controllers/DrawioImageController.php | 14 ++-- .../Controllers/GalleryImageController.php | 18 +++-- .../Controllers/ImageGalleryApiController.php | 1 + tests/Activity/CommentMentionTest.php | 2 +- tests/Api/ContentPermissionsApiTest.php | 4 +- tests/Api/ImageGalleryApiTest.php | 28 ++++++++ .../RegeneratePermissionsCommandTest.php | 2 +- tests/ErrorTest.php | 3 +- tests/Helpers/PermissionsProvider.php | 3 +- .../Scenarios/EntityRolePermissionsTest.php | 66 +++++++++---------- tests/PublicActionTest.php | 3 +- tests/Uploads/DrawioTest.php | 24 +++++++ tests/Uploads/ImageTest.php | 39 +++++++++++ 14 files changed, 155 insertions(+), 53 deletions(-) diff --git a/app/Config/database.php b/app/Config/database.php index 6fc861312d4..86bae5f5b63 100644 --- a/app/Config/database.php +++ b/app/Config/database.php @@ -81,7 +81,6 @@ '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'), ]) : [], ], diff --git a/app/Uploads/Controllers/DrawioImageController.php b/app/Uploads/Controllers/DrawioImageController.php index 8295febc1c1..53ae7900408 100644 --- a/app/Uploads/Controllers/DrawioImageController.php +++ b/app/Uploads/Controllers/DrawioImageController.php @@ -2,6 +2,7 @@ namespace BookStack\Uploads\Controllers; +use BookStack\Entities\Queries\PageQueries; use BookStack\Exceptions\ImageUploadException; use BookStack\Http\Controller; use BookStack\Permissions\Permission; @@ -14,7 +15,8 @@ class DrawioImageController extends Controller { public function __construct( - protected ImageRepo $imageRepo + protected ImageRepo $imageRepo, + protected PageQueries $pageQueries, ) { } @@ -53,16 +55,18 @@ public function list(Request $request, ImageResizer $resizer) */ public function create(Request $request) { - $this->validate($request, [ + $this->checkPermission(Permission::ImageCreateAll); + $validated = $this->validate($request, [ 'image' => ['required', 'string'], 'uploaded_to' => ['required', 'integer'], ]); - $this->checkPermission(Permission::ImageCreateAll); - $imageBase64Data = $request->input('image'); + $imageBase64Data = $validated['image']; + $uploadedTo = $validated['uploaded_to']; + $targetPage = $this->pageQueries->findVisibleByIdOrFail($uploadedTo); + $this->checkOwnablePermission(Permission::PageUpdate, $targetPage); try { - $uploadedTo = $request->input('uploaded_to', 0); $image = $this->imageRepo->saveDrawing($imageBase64Data, $uploadedTo); } catch (ImageUploadException $e) { return response($e->getMessage(), 500); diff --git a/app/Uploads/Controllers/GalleryImageController.php b/app/Uploads/Controllers/GalleryImageController.php index 908322be07f..ca60f9f8e19 100644 --- a/app/Uploads/Controllers/GalleryImageController.php +++ b/app/Uploads/Controllers/GalleryImageController.php @@ -2,6 +2,7 @@ namespace BookStack\Uploads\Controllers; +use BookStack\Entities\Queries\PageQueries; use BookStack\Exceptions\ImageUploadException; use BookStack\Http\Controller; use BookStack\Permissions\Permission; @@ -14,7 +15,8 @@ class GalleryImageController extends Controller { public function __construct( - protected ImageRepo $imageRepo + protected ImageRepo $imageRepo, + protected PageQueries $pageQueries, ) { } @@ -56,20 +58,26 @@ public function create(Request $request) $this->checkPermission(Permission::ImageCreateAll); try { - $this->validate($request, [ + $validated = $this->validate($request, [ 'file' => $this->getImageValidationRules(), + 'uploaded_to' => ['required', 'integer'], ]); } catch (ValidationException $exception) { - return $this->jsonError(implode("\n", $exception->errors()['file'])); + $errors = $exception->errors(); + $messages = array_merge($errors['file'] ?? [], $errors['uploaded_to'] ?? []); + return $this->jsonError(implode("\n", $messages)); } + $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 = $request->file('file'); - $uploadedTo = $request->input('uploaded_to', 0); + $imageUpload = $validated['file']; $image = $this->imageRepo->saveNew($imageUpload, 'gallery', $uploadedTo); } catch (ImageUploadException $e) { return response($e->getMessage(), 500); diff --git a/app/Uploads/Controllers/ImageGalleryApiController.php b/app/Uploads/Controllers/ImageGalleryApiController.php index c4168a77e94..6a72e4c30e4 100644 --- a/app/Uploads/Controllers/ImageGalleryApiController.php +++ b/app/Uploads/Controllers/ImageGalleryApiController.php @@ -75,6 +75,7 @@ public function create(Request $request) $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); diff --git a/tests/Activity/CommentMentionTest.php b/tests/Activity/CommentMentionTest.php index 7f26f8689d2..b9701b11577 100644 --- a/tests/Activity/CommentMentionTest.php +++ b/tests/Activity/CommentMentionTest.php @@ -117,7 +117,7 @@ public function test_notification_limited_to_those_with_view_permissions() $page = $this->entities->page(); $this->permissions->disableEntityInheritedPermissions($page); - $this->permissions->addEntityPermission($page, ['view'], $userA->roles()->first()); + $this->permissions->setEntityPermissionsForRole($page, ['view'], $userA->roles()->first()); $this->asAdmin()->post("/comment/{$page->id}", [ 'html' => '

    Hello and

    ' diff --git a/tests/Api/ContentPermissionsApiTest.php b/tests/Api/ContentPermissionsApiTest.php index 464d62683ad..6a26b403245 100644 --- a/tests/Api/ContentPermissionsApiTest.php +++ b/tests/Api/ContentPermissionsApiTest.php @@ -39,7 +39,7 @@ public function test_read_endpoint_shows_expected_detail() $page = $this->entities->page(); $owner = $this->users->newUser(); $role = $this->users->createRole(); - $this->permissions->addEntityPermission($page, ['view', 'delete'], $role); + $this->permissions->setEntityPermissionsForRole($page, ['view', 'delete'], $role); $this->permissions->changeEntityOwner($page, $owner); $this->permissions->setFallbackPermissions($page, ['update', 'create']); @@ -209,7 +209,7 @@ public function test_update_can_set_fallback_permissions() public function test_update_can_clear_roles_permissions() { $page = $this->entities->page(); - $this->permissions->addEntityPermission($page, ['view'], $this->users->createRole()); + $this->permissions->setEntityPermissionsForRole($page, ['view'], $this->users->createRole()); $page->owned_by = null; $page->save(); diff --git a/tests/Api/ImageGalleryApiTest.php b/tests/Api/ImageGalleryApiTest.php index 07c20c83416..09dba84f548 100644 --- a/tests/Api/ImageGalleryApiTest.php +++ b/tests/Api/ImageGalleryApiTest.php @@ -154,6 +154,34 @@ public function test_create_fails_if_uploaded_to_not_visible_or_not_exists() $resp->assertStatus(404); } + public function test_create_requires_update_permission_for_the_target_page() + { + $editor = $this->users->editor(); + $this->actingAsForApi($editor); + + $makeRequest = function (int $uploadedTo) { + return $this->call('POST', $this->baseEndpoint, [ + 'type' => 'gallery', + 'uploaded_to' => $uploadedTo, + 'name' => 'My awesome image!', + ], [], [ + 'image' => $this->files->uploadedImage('my-cool-image.png'), + ]); + }; + + $page = $this->entities->page(); + $this->permissions->disableEntityInheritedPermissions($page); + $this->permissions->setEntityPermissionsForRole($page, ['view'], $editor->roles()->first()); + + $resp = $makeRequest($page->id); + $resp->assertStatus(403); + + $this->permissions->setEntityPermissionsForRole($page, ['view', 'update'], $editor->roles()->first()); + + $resp = $makeRequest($page->id); + $resp->assertStatus(200); + } + public function test_create_has_restricted_types() { $this->actingAsApiEditor(); diff --git a/tests/Commands/RegeneratePermissionsCommandTest.php b/tests/Commands/RegeneratePermissionsCommandTest.php index 75c6c1b3851..27a339fbe1f 100644 --- a/tests/Commands/RegeneratePermissionsCommandTest.php +++ b/tests/Commands/RegeneratePermissionsCommandTest.php @@ -16,7 +16,7 @@ public function test_regen_permissions_command() $page = $this->entities->page(); $editor = $this->users->editor(); $role = $editor->roles()->first(); - $this->permissions->addEntityPermission($page, ['view'], $role); + $this->permissions->setEntityPermissionsForRole($page, ['view'], $role); JointPermission::query()->truncate(); $this->assertDatabaseMissing('joint_permissions', ['entity_id' => $page->id]); diff --git a/tests/ErrorTest.php b/tests/ErrorTest.php index 642945d4388..27c83e212b6 100644 --- a/tests/ErrorTest.php +++ b/tests/ErrorTest.php @@ -2,7 +2,6 @@ namespace Tests; -use Illuminate\Foundation\Http\Middleware\ValidatePostSize; use Illuminate\Support\Facades\Log; class ErrorTest extends TestCase @@ -44,7 +43,7 @@ public function test_404_page_shows_visible_content_within_non_visible_parent() $this->actingAs($editor)->get($page->getUrl())->assertOk(); $this->permissions->disableEntityInheritedPermissions($book); - $this->permissions->addEntityPermission($page, ['view'], $editor->roles()->first()); + $this->permissions->setEntityPermissionsForRole($page, ['view'], $editor->roles()->first()); $resp = $this->actingAs($editor)->get($book->getUrl()); $resp->assertNotFound(); diff --git a/tests/Helpers/PermissionsProvider.php b/tests/Helpers/PermissionsProvider.php index c9ae309196e..9b12ee985b3 100644 --- a/tests/Helpers/PermissionsProvider.php +++ b/tests/Helpers/PermissionsProvider.php @@ -101,8 +101,9 @@ public function setEntityPermissions(Entity $entity, array $actions = [], array $this->addEntityPermissionEntries($entity, $permissions); } - public function addEntityPermission(Entity $entity, array $actionList, Role $role) + public function setEntityPermissionsForRole(Entity $entity, array $actionList, Role $role) { + $entity->permissions()->where('role_id', '=', $role->id)->delete(); $permissionData = $this->actionListToEntityPermissionData($actionList, $role->id); $this->addEntityPermissionEntries($entity, [$permissionData]); } diff --git a/tests/Permissions/Scenarios/EntityRolePermissionsTest.php b/tests/Permissions/Scenarios/EntityRolePermissionsTest.php index 55761e08c58..fa89d365c0a 100644 --- a/tests/Permissions/Scenarios/EntityRolePermissionsTest.php +++ b/tests/Permissions/Scenarios/EntityRolePermissionsTest.php @@ -29,8 +29,8 @@ public function test_03_same_level_conflicting() $page = $this->entities->page(); $this->permissions->disableEntityInheritedPermissions($page); - $this->permissions->addEntityPermission($page, [], $roleA); - $this->permissions->addEntityPermission($page, ['view'], $roleB); + $this->permissions->setEntityPermissionsForRole($page, [], $roleA); + $this->permissions->setEntityPermissionsForRole($page, ['view'], $roleB); $this->assertVisibleToUser($page, $user); } @@ -42,7 +42,7 @@ public function test_20_inherit_allow() $chapter = $page->chapter; $this->permissions->disableEntityInheritedPermissions($chapter); - $this->permissions->addEntityPermission($chapter, ['view'], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, ['view'], $roleA); $this->assertVisibleToUser($page, $user); } @@ -54,7 +54,7 @@ public function test_21_inherit_deny() $chapter = $page->chapter; $this->permissions->disableEntityInheritedPermissions($chapter); - $this->permissions->addEntityPermission($chapter, [], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, [], $roleA); $this->assertNotVisibleToUser($page, $user); } @@ -67,8 +67,8 @@ public function test_22_same_level_conflict_inherit() $chapter = $page->chapter; $this->permissions->disableEntityInheritedPermissions($chapter); - $this->permissions->addEntityPermission($chapter, [], $roleA); - $this->permissions->addEntityPermission($chapter, ['view'], $roleB); + $this->permissions->setEntityPermissionsForRole($chapter, [], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, ['view'], $roleB); $this->assertVisibleToUser($page, $user); } @@ -80,8 +80,8 @@ public function test_30_child_inherit_override_allow() $chapter = $page->chapter; $this->permissions->disableEntityInheritedPermissions($chapter); - $this->permissions->addEntityPermission($chapter, [], $roleA); - $this->permissions->addEntityPermission($page, ['view'], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, [], $roleA); + $this->permissions->setEntityPermissionsForRole($page, ['view'], $roleA); $this->assertVisibleToUser($page, $user); } @@ -93,8 +93,8 @@ public function test_31_child_inherit_override_deny() $chapter = $page->chapter; $this->permissions->disableEntityInheritedPermissions($chapter); - $this->permissions->addEntityPermission($chapter, ['view'], $roleA); - $this->permissions->addEntityPermission($page, [], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, ['view'], $roleA); + $this->permissions->setEntityPermissionsForRole($page, [], $roleA); $this->assertNotVisibleToUser($page, $user); } @@ -107,8 +107,8 @@ public function test_40_multi_role_inherit_conflict_override_deny() $chapter = $page->chapter; $this->permissions->disableEntityInheritedPermissions($chapter); - $this->permissions->addEntityPermission($page, [], $roleA); - $this->permissions->addEntityPermission($chapter, ['view'], $roleB); + $this->permissions->setEntityPermissionsForRole($page, [], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, ['view'], $roleB); $this->assertVisibleToUser($page, $user); } @@ -121,8 +121,8 @@ public function test_41_multi_role_inherit_conflict_retain_allow() $chapter = $page->chapter; $this->permissions->disableEntityInheritedPermissions($chapter); - $this->permissions->addEntityPermission($page, ['view'], $roleA); - $this->permissions->addEntityPermission($chapter, [], $roleB); + $this->permissions->setEntityPermissionsForRole($page, ['view'], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, [], $roleB); $this->assertVisibleToUser($page, $user); } @@ -131,7 +131,7 @@ public function test_50_role_override_allow() { [$user, $roleA] = $this->users->newUserWithRole(); $page = $this->entities->page(); - $this->permissions->addEntityPermission($page, ['view'], $roleA); + $this->permissions->setEntityPermissionsForRole($page, ['view'], $roleA); $this->assertVisibleToUser($page, $user); } @@ -140,7 +140,7 @@ public function test_51_role_override_deny() { [$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']); $page = $this->entities->page(); - $this->permissions->addEntityPermission($page, [], $roleA); + $this->permissions->setEntityPermissionsForRole($page, [], $roleA); $this->assertNotVisibleToUser($page, $user); } @@ -150,7 +150,7 @@ public function test_60_inherited_role_override_allow() [$user, $roleA] = $this->users->newUserWithRole([], []); $page = $this->entities->pageWithinChapter(); $chapter = $page->chapter; - $this->permissions->addEntityPermission($chapter, ['view'], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, ['view'], $roleA); $this->assertVisibleToUser($page, $user); } @@ -160,7 +160,7 @@ public function test_61_inherited_role_override_deny() [$user, $roleA] = $this->users->newUserWithRole([], ['page-view-all']); $page = $this->entities->pageWithinChapter(); $chapter = $page->chapter; - $this->permissions->addEntityPermission($chapter, [], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, [], $roleA); $this->assertNotVisibleToUser($page, $user); } @@ -170,7 +170,7 @@ public function test_62_inherited_role_override_deny_on_own() [$user, $roleA] = $this->users->newUserWithRole([], ['page-view-own']); $page = $this->entities->pageWithinChapter(); $chapter = $page->chapter; - $this->permissions->addEntityPermission($chapter, [], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, [], $roleA); $this->permissions->changeEntityOwner($page, $user); $this->assertNotVisibleToUser($page, $user); @@ -182,7 +182,7 @@ public function test_70_multi_role_inheriting_deny() $roleB = $this->users->attachNewRole($user); $page = $this->entities->page(); - $this->permissions->addEntityPermission($page, [], $roleB); + $this->permissions->setEntityPermissionsForRole($page, [], $roleB); $this->assertNotVisibleToUser($page, $user); } @@ -194,7 +194,7 @@ public function test_71_multi_role_inheriting_deny_on_own() $page = $this->entities->page(); $this->permissions->changeEntityOwner($page, $user); - $this->permissions->addEntityPermission($page, [], $roleB); + $this->permissions->setEntityPermissionsForRole($page, [], $roleB); $this->assertNotVisibleToUser($page, $user); } @@ -207,7 +207,7 @@ public function test_75_multi_role_inherited_deny_via_parent() $page = $this->entities->pageWithinChapter(); $chapter = $page->chapter; - $this->permissions->addEntityPermission($chapter, [], $roleB); + $this->permissions->setEntityPermissionsForRole($chapter, [], $roleB); $this->assertNotVisibleToUser($page, $user); } @@ -220,7 +220,7 @@ public function test_76_multi_role_inherited_deny_via_parent_on_own() $chapter = $page->chapter; $this->permissions->changeEntityOwner($page, $user); - $this->permissions->addEntityPermission($chapter, [], $roleB); + $this->permissions->setEntityPermissionsForRole($chapter, [], $roleB); $this->assertNotVisibleToUser($page, $user); } @@ -231,7 +231,7 @@ public function test_80_fallback_override_allow() $page = $this->entities->page(); $this->permissions->setFallbackPermissions($page, []); - $this->permissions->addEntityPermission($page, ['view'], $roleA); + $this->permissions->setEntityPermissionsForRole($page, ['view'], $roleA); $this->assertVisibleToUser($page, $user); } @@ -241,7 +241,7 @@ public function test_81_fallback_override_deny() $page = $this->entities->page(); $this->permissions->setFallbackPermissions($page, ['view']); - $this->permissions->addEntityPermission($page, [], $roleA); + $this->permissions->setEntityPermissionsForRole($page, [], $roleA); $this->assertNotVisibleToUser($page, $user); } @@ -253,7 +253,7 @@ public function test_84_fallback_override_allow_multi_role() $page = $this->entities->page(); $this->permissions->setFallbackPermissions($page, []); - $this->permissions->addEntityPermission($page, ['view'], $roleA); + $this->permissions->setEntityPermissionsForRole($page, ['view'], $roleA); $this->assertVisibleToUser($page, $user); } @@ -265,7 +265,7 @@ public function test_85_fallback_override_deny_multi_role() $page = $this->entities->page(); $this->permissions->setFallbackPermissions($page, ['view']); - $this->permissions->addEntityPermission($page, [], $roleA); + $this->permissions->setEntityPermissionsForRole($page, [], $roleA); $this->assertNotVisibleToUser($page, $user); } @@ -277,7 +277,7 @@ public function test_86_fallback_override_allow_inherit() $chapter = $page->chapter; $this->permissions->setFallbackPermissions($chapter, []); - $this->permissions->addEntityPermission($chapter, ['view'], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, ['view'], $roleA); $this->assertVisibleToUser($page, $user); } @@ -289,7 +289,7 @@ public function test_87_fallback_override_deny_inherit() $chapter = $page->chapter; $this->permissions->setFallbackPermissions($chapter, ['view']); - $this->permissions->addEntityPermission($chapter, [], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, [], $roleA); $this->assertNotVisibleToUser($page, $user); } @@ -302,7 +302,7 @@ public function test_88_fallback_override_allow_multi_role_inherit() $chapter = $page->chapter; $this->permissions->setFallbackPermissions($chapter, []); - $this->permissions->addEntityPermission($chapter, ['view'], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, ['view'], $roleA); $this->assertVisibleToUser($page, $user); } @@ -315,7 +315,7 @@ public function test_89_fallback_override_deny_multi_role_inherit() $chapter = $page->chapter; $this->permissions->setFallbackPermissions($chapter, ['view']); - $this->permissions->addEntityPermission($chapter, [], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, [], $roleA); $this->assertNotVisibleToUser($page, $user); } @@ -328,7 +328,7 @@ public function test_90_fallback_overrides_parent_entity_role_deny() $this->permissions->setFallbackPermissions($chapter, []); $this->permissions->setFallbackPermissions($page, []); - $this->permissions->addEntityPermission($chapter, ['view'], $roleA); + $this->permissions->setEntityPermissionsForRole($chapter, ['view'], $roleA); $this->assertNotVisibleToUser($page, $user); } @@ -342,7 +342,7 @@ public function test_91_fallback_overrides_parent_entity_role_inherit() $this->permissions->setFallbackPermissions($book, []); $this->permissions->setFallbackPermissions($chapter, []); - $this->permissions->addEntityPermission($book, ['view'], $roleA); + $this->permissions->setEntityPermissionsForRole($book, ['view'], $roleA); $this->assertNotVisibleToUser($page, $user); } diff --git a/tests/PublicActionTest.php b/tests/PublicActionTest.php index e6fc7a6a3c5..5b0f5353a7f 100644 --- a/tests/PublicActionTest.php +++ b/tests/PublicActionTest.php @@ -6,7 +6,6 @@ use BookStack\Entities\Models\Chapter; use BookStack\Permissions\Models\RolePermission; use BookStack\Users\Models\Role; -use BookStack\Users\Models\User; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\View; @@ -173,7 +172,7 @@ public function test_public_view_can_take_on_other_roles() $newRole = $this->users->attachNewRole($this->users->guest(), []); $page = $this->entities->page(); $this->permissions->disableEntityInheritedPermissions($page); - $this->permissions->addEntityPermission($page, ['view', 'update'], $newRole); + $this->permissions->setEntityPermissionsForRole($page, ['view', 'update'], $newRole); $resp = $this->get($page->getUrl()); $resp->assertOk(); diff --git a/tests/Uploads/DrawioTest.php b/tests/Uploads/DrawioTest.php index d5b3f60880e..faa2a0bfb11 100644 --- a/tests/Uploads/DrawioTest.php +++ b/tests/Uploads/DrawioTest.php @@ -72,6 +72,30 @@ public function test_drawing_base64_upload() $this->assertTrue($testImageData === $uploadedImageData, 'Uploaded image file data does not match our test image as expected'); } + public function test_base64_upload_requires_edit_permission_to_page() + { + $page = $this->entities->page(); + $editor = $this->users->editor(); + $this->actingAs($editor); + + $this->permissions->disableEntityInheritedPermissions($page); + + $upload = function () use ($page) { + return $this->postJson('images/drawio', [ + 'uploaded_to' => $page->id, + 'image' => 'image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEcDCo5iYNs+gAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAFElEQVQI12O0jN/KgASYGFABqXwAZtoBV6Sl3hIAAAAASUVORK5CYII=', + ]); + }; + + $upload()->assertStatus(404); + + $this->permissions->setEntityPermissionsForRole($page, ['view'], $editor->roles()->first()); + $upload()->assertStatus(403); + + $this->permissions->setEntityPermissionsForRole($page, ['view', 'update'], $editor->roles()->first()); + $upload()->assertStatus(200); + } + public function test_drawio_url_can_be_configured() { config()->set('services.drawio', 'http://cats.com?dog=tree'); diff --git a/tests/Uploads/ImageTest.php b/tests/Uploads/ImageTest.php index 1bccee2c395..ac7bf31e4c4 100644 --- a/tests/Uploads/ImageTest.php +++ b/tests/Uploads/ImageTest.php @@ -36,6 +36,45 @@ public function test_image_upload() ]); } + public function test_image_upload_with_page_reference_requires_visibility_and_update_permissions_of_target_page() + { + $page = $this->entities->page(); + $editor = $this->users->editor(); + + $this->permissions->disableEntityInheritedPermissions($page); + $this->actingAs($editor); + + $resp = $this->files->uploadGalleryImage($this, 'test-image.png', $page->id); + $resp->assertStatus(404); + + $this->permissions->setEntityPermissionsForRole($page, ['view'], $editor->roles()->first()); + + $resp = $this->files->uploadGalleryImage($this, 'test-image.png', $page->id); + $this->assertPermissionError($resp); + + $this->permissions->setEntityPermissionsForRole($page, ['view', 'update'], $editor->roles()->first()); + + $resp = $this->files->uploadGalleryImage($this, 'test-image.png', $page->id); + $resp->assertStatus(200); + + $this->files->deleteAtRelativePath($resp->json('path')); + } + + public function test_image_upload_with_page_reference_requires_page_to_exist() + { + $page = $this->entities->page(); + $this->entities->destroy($page); + + $editor = $this->users->editor(); + $this->actingAs($editor); + + $resp = $this->files->uploadGalleryImage($this, 'test-image.png', $page->id); + $resp->assertStatus(404); + + $resp = $this->files->uploadGalleryImage($this, 'test-image.png', 0); + $resp->assertStatus(404); + } + public function test_image_display_thumbnail_generation_does_not_increase_image_size() { $page = $this->entities->page(); From 39a14cff8f64f51fa3b4be2f4c2a7b85d7c2b600 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 17 May 2026 13:00:38 +0100 Subject: [PATCH 161/204] User MFA: Reviewed addition of reset, added tests Review of #6056 Added test coverage. --- app/Activity/ActivityType.php | 1 + app/Users/Controllers/UserController.php | 10 ++-- lang/en/activities.php | 2 + lang/en/settings.php | 8 ++-- resources/views/users/edit.blade.php | 44 ++++++++++++------ routes/web.php | 2 +- tests/User/UserManagementMfaTest.php | 59 ++++++++++++++++++++++++ 7 files changed, 102 insertions(+), 24 deletions(-) create mode 100644 tests/User/UserManagementMfaTest.php diff --git a/app/Activity/ActivityType.php b/app/Activity/ActivityType.php index a7f129f71d4..64532de175c 100644 --- a/app/Activity/ActivityType.php +++ b/app/Activity/ActivityType.php @@ -46,6 +46,7 @@ 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'; diff --git a/app/Users/Controllers/UserController.php b/app/Users/Controllers/UserController.php index ba85f99fbd0..f7310438122 100644 --- a/app/Users/Controllers/UserController.php +++ b/app/Users/Controllers/UserController.php @@ -4,6 +4,7 @@ use BookStack\Access\SocialDriverManager; use BookStack\Access\UserInviteException; +use BookStack\Activity\ActivityType; use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\UserUpdateException; use BookStack\Http\Controller; @@ -214,11 +215,14 @@ public function destroy(Request $request, int $id) */ public function resetMfa(Request $request, int $id) { + $this->preventAccessInDemoMode(); $this->checkPermission(Permission::UsersManage); + $user = $this->userRepo->getById($id); - // Resetear el 2FA del usuario $user->mfaValues()->delete(); - session()->flash('success', trans('settings.users_mfa_reset_success', ['userName' => $user->name])); - return redirect()->back(); + + $this->logActivity(ActivityType::USER_MFA_RESET, $user); + + return redirect("/settings/users/{$user->id}"); } } diff --git a/lang/en/activities.php b/lang/en/activities.php index 4362fc02958..e9344a3d477 100644 --- a/lang/en/activities.php +++ b/lang/en/activities.php @@ -99,6 +99,8 @@ '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', diff --git a/lang/en/settings.php b/lang/en/settings.php index 920c3c1bc02..d03024a89d6 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -264,11 +264,9 @@ '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 2FA', - 'users_mfa_reset_desc' => 'Reset and clear all configured MFA methods for :userName. They will be prompted to reconfigure on next login.', - 'users_mfa_reset_confirm' => 'Are you sure you want to reset 2FA for :userName?', - 'users_mfa_reset_success' => '2FA has been reset for :userName', - 'users_mfa_reset_error' => 'Failed to reset 2FA for :userName', + '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', diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index 64d45f50361..8a4a8bcce79 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -71,21 +71,35 @@ class="button outline">{{ trans('settings.users_mfa_configure') }}
  • - @if(user()->hasSystemRole('admin')) -
    -
    -
    -
    - {{ trans('settings.users_mfa_reset') }} -

    {{ trans('settings.users_mfa_reset_desc', ['userName' => $user->name]) }}

    -
    -
    -
    id}/reset-mfa") }}" method="POST" style="display: inline;"> - @csrf - + @if($mfaMethods->count() > 0) +
    + +
    +
    +

    {{ trans('settings.users_mfa_reset_desc') }}

    + id}/mfa") }}" method="POST" style="display: inline;"> + {{ csrf_field() }} + {{ method_field('DELETE') }} +
    diff --git a/routes/web.php b/routes/web.php index 2571da2f3a2..3e3b5aef37d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -251,7 +251,7 @@ Route::get('/settings/users/{id}', [UserControllers\UserController::class, 'edit']); Route::put('/settings/users/{id}', [UserControllers\UserController::class, 'update']); Route::delete('/settings/users/{id}', [UserControllers\UserController::class, 'destroy']); - Route::post('/settings/users/{id}/reset-mfa', [UserControllers\UserController::class, 'resetMfa']); + Route::delete('/settings/users/{id}/mfa', [UserControllers\UserController::class, 'resetMfa']); // User Account Route::get('/my-account', [UserControllers\UserAccountController::class, 'redirect']); diff --git a/tests/User/UserManagementMfaTest.php b/tests/User/UserManagementMfaTest.php new file mode 100644 index 00000000000..ea05869cb0e --- /dev/null +++ b/tests/User/UserManagementMfaTest.php @@ -0,0 +1,59 @@ +users->editor(); + + $resp = $this->asAdmin()->get($editor->getEditUrl()); + $resp->assertSeeText('0 methods configured'); + + MfaValue::factory()->create(['user_id' => $editor->id, 'method' => MfaValue::METHOD_BACKUP_CODES]); + + $resp = $this->get($editor->getEditUrl()); + $resp->assertSeeText('1 method configured'); + $resp->assertDontSeeText('0 methods configured'); + } + + public function test_reset_mfa_flow() + { + $editor = $this->users->editor(); + MfaValue::factory()->create(['user_id' => $editor->id, 'method' => MfaValue::METHOD_BACKUP_CODES]); + MfaValue::factory()->create(['user_id' => $editor->id, 'method' => MfaValue::METHOD_TOTP]); + + $this->assertEquals(2, $editor->mfaValues()->count()); + + $resp = $this->asAdmin()->get($editor->getEditUrl()); + $this->withHtml($resp)->assertElementContains('form[action$="/mfa"] button[type="submit"]', 'Reset'); + + $resp = $this->delete($editor->getEditUrl('/mfa')); + $resp->assertRedirect($editor->getEditUrl()); + $this->assertActivityExists(ActivityType::USER_MFA_RESET); + + $resp = $this->followRedirects($resp); + $resp->assertSee('Multi-factor authentication methods reset'); + + $this->assertEquals(0, $editor->mfaValues()->count()); + } + + public function test_users_manage_permission_required_for_mfa_reset() + { + $editor = $this->users->editor(); + $resp = $this->actingAs($editor)->delete($editor->getEditUrl('/mfa')); + $this->assertPermissionError($resp); + + $this->permissions->grantUserRolePermissions($editor, [Permission::UsersManage]); + + $resp = $this->delete($editor->getEditUrl('/mfa')); + $this->assertNotPermissionError($resp); + $resp->assertRedirect($editor->getEditUrl()); + } +} From 0cd773a5d3d07b1299660c22a3f65456b6aa28d0 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 17 May 2026 18:33:23 +0100 Subject: [PATCH 162/204] CSP Headers: Review of #6071 - Removed extra non-needed docs in repo - Tweaked some wording. - Added extra test scenarios. - Added options to phpunit default env. - Added auto-quote-handling for unsafe-inline CSS rule. For #6033 --- .env.example.complete | 5 +++-- app/Config/app.php | 3 ++- app/Util/CspService.php | 10 ++++++++- dev/docs/development.md | 42 ------------------------------------ phpunit.xml | 2 ++ readme.md | 1 - tests/SecurityHeaderTest.php | 35 ++++++++++++++++++++++++++++-- 7 files changed, 49 insertions(+), 49 deletions(-) diff --git a/.env.example.complete b/.env.example.complete index 6a7f9db6529..ed0cac5f473 100644 --- a/.env.example.complete +++ b/.env.example.complete @@ -402,9 +402,10 @@ ALLOWED_IFRAME_SOURCES="https://*.draw.io https://*.youtube.com https://*.youtub ALLOWED_CSS_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. +# 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 data:" +# 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. diff --git a/app/Config/app.php b/app/Config/app.php index 5536e9abdd4..12f285ea9e1 100644 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -78,7 +78,8 @@ 'css_sources' => env('ALLOWED_CSS_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. + // 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), diff --git a/app/Util/CspService.php b/app/Util/CspService.php index a0e1faadf95..68c544ff09d 100644 --- a/app/Util/CspService.php +++ b/app/Util/CspService.php @@ -175,6 +175,14 @@ protected function getAllowedStyleSources(): array $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)); } @@ -195,7 +203,7 @@ protected function getAllowedImageSources(): array if (is_string($configured)) { $sources = array_filter(explode(' ', $configured)); - array_unshift($sources, "'self'"); + array_unshift($sources, "'self'", 'blob:', 'data:'); return array_values(array_unique($sources)); } diff --git a/dev/docs/development.md b/dev/docs/development.md index 16f168f9cda..2c73a0256c5 100644 --- a/dev/docs/development.md +++ b/dev/docs/development.md @@ -31,48 +31,6 @@ BookStack has a large suite of PHP tests to cover application functionality. We For details about setting-up, running and writing tests please see the [php-testing.md document](php-testing.md). -## Content Security Policy Controls - -BookStack enforces a Content Security Policy (CSP) response header to reduce risk from injected content and untrusted embeds. - -For backward compatibility, image and CSS controls are intentionally permissive by default, but can be tightened via environment options. - -### Related Environment Options - -These values are defined in `.env.example.complete`: - -- `ALLOWED_CSS_SOURCES` - - Controls allowed `style-src` sources. - - Defaults to a permissive fallback if unset. -- `ALLOWED_IMAGE_SOURCES` - - Controls allowed `img-src` sources. - - Defaults to a permissive fallback if unset. - -Values should be space-separated source expressions. - -### Example Configurations - -Allow Google Fonts CSS and local styles only: - -```bash -ALLOWED_CSS_SOURCES="https://fonts.googleapis.com" -``` - -Allow local images, embedded data images, and a dedicated image CDN: - -```bash -ALLOWED_IMAGE_SOURCES="data: https://images.example.com" -``` - -### Tightening Guidance - -When hardening a deployment: - -1. Start with defaults to avoid unexpected breakage. -2. Set explicit `ALLOWED_CSS_SOURCES` and `ALLOWED_IMAGE_SOURCES` values for the domains you actually use. -3. Test key workflows (editor, page display, theme assets, external embeds) and browser console CSP warnings. -4. Remove unnecessary protocols and hosts over time. - ## Code Standards We use tools to manage code standards and formatting within the project. If submitting a PR, formatting as per our project standards would help for clarity but don't worry too much about using/understanding these tools as we can always address issues at a later stage when they're picked up by our automated tools. diff --git a/phpunit.xml b/phpunit.xml index 94fc002b704..52c5e7de8e9 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -18,6 +18,8 @@ + + diff --git a/readme.md b/readme.md index e231ad9f0ba..e9e5d197b43 100644 --- a/readme.md +++ b/readme.md @@ -100,7 +100,6 @@ Big thanks to these companies for supporting the project. ## 🛠️ Development & Testing Please see our [development docs](dev/docs/development.md) for full details regarding work on the BookStack source code. -For details on Content Security Policy controls (including image and CSS source options), see the **Content Security Policy Controls** section in the [development docs](dev/docs/development.md). If you're just looking to customize or extend your own BookStack instance, take a look at our [Hacking BookStack documentation page](https://www.bookstackapp.com/docs/admin/hacking-bookstack/) for details on various options to achieve this without altering the BookStack source code. diff --git a/tests/SecurityHeaderTest.php b/tests/SecurityHeaderTest.php index 126a85f238c..8badc39b9b3 100644 --- a/tests/SecurityHeaderTest.php +++ b/tests/SecurityHeaderTest.php @@ -153,6 +153,7 @@ public function test_frame_src_csp_header_drawio_host_includes_port_if_existing( public function test_style_src_csp_header_set_to_permissive_defaults_when_not_configured() { + config()->set('app.css_sources', null); $resp = $this->get('/'); $header = $this->getCspHeader($resp, 'style-src'); @@ -169,8 +170,29 @@ public function test_style_src_csp_header_can_be_overridden_by_config() $this->assertEquals("style-src 'self' https://fonts.example.com", $header); } + public function test_style_src_csp_header_unsafe_inline_value_will_be_auto_quoted() + { + config()->set('app.css_sources', 'unsafe-inline https://css.example.com'); + + $resp = $this->get('/'); + $header = $this->getCspHeader($resp, 'style-src'); + + $this->assertEquals("style-src 'self' 'unsafe-inline' https://css.example.com", $header); + } + + public function test_style_src_can_be_blank_to_set_no_additions() + { + config()->set('app.css_sources', ''); + + $resp = $this->get('/'); + $header = $this->getCspHeader($resp, 'style-src'); + + $this->assertEquals("style-src 'self'", $header); + } + public function test_img_src_csp_header_set_to_permissive_defaults_when_not_configured() { + config()->set('app.image_sources', null); $resp = $this->get('/'); $header = $this->getCspHeader($resp, 'img-src'); @@ -179,12 +201,21 @@ public function test_img_src_csp_header_set_to_permissive_defaults_when_not_conf public function test_img_src_csp_header_can_be_overridden_by_config() { - config()->set('app.image_sources', 'https://images.example.com data:'); + config()->set('app.image_sources', 'https://images.example.com'); + + $resp = $this->get('/'); + $header = $this->getCspHeader($resp, 'img-src'); + $this->assertEquals("img-src 'self' blob: data: https://images.example.com", $header); + } + + public function test_img_src_can_be_blank_to_set_no_additions() + { + config()->set('app.image_sources', ''); $resp = $this->get('/'); $header = $this->getCspHeader($resp, 'img-src'); - $this->assertEquals("img-src 'self' https://images.example.com data:", $header); + $this->assertEquals("img-src 'self' blob: data:", $header); } public function test_cache_control_headers_are_set_on_responses() From b5d3ba2726dc2f3792f580a8d00cf8d6bbe01126 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 18 May 2026 17:39:00 +0100 Subject: [PATCH 163/204] Testing: Added extra page edit test Added during investigation for #6062 Might as well leave in even though it does not trigger the cause for that particuluar issue. --- tests/Entity/PageTest.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Entity/PageTest.php b/tests/Entity/PageTest.php index 1b2f3c9fe5a..0da76b8e92f 100644 --- a/tests/Entity/PageTest.php +++ b/tests/Entity/PageTest.php @@ -273,4 +273,29 @@ public function test_page_edit_without_update_permissions_but_with_view_redirect $resp->assertSessionHas('error', 'You do not have permission to access the requested page.'); } + + public function test_page_html_content_remains_stable_through_re_edit_and_does_not_create_revision() + { + $page = $this->entities->page(); + + $this->asEditor()->put($page->getUrl(), [ + 'name' => 'Stability Test', + 'html' => '
    a
    ', + ]); + $initialRevisionCount = $page->revisions()->count(); + $page->refresh(); + + // Get the page content from the edit view to ensure we're using the + // exact same content which would be loaded into the editor. + $editView = $this->get($page->getUrl('/edit')); + $htmlContentEncoded = $this->withHtml($editView)->getInnerHtml('textarea#html-editor'); + $htmlContent = html_entity_decode($htmlContentEncoded); + + $this->asEditor()->put($page->getUrl(), [ + 'name' => 'Stability Test', + 'html' => $htmlContent, + ]); + + $this->assertEquals($initialRevisionCount, $page->revisions()->count()); + } } From 5ebfa65a46a0ee0d1ef9c54140ac2bbd3dbd59e6 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 19 May 2026 13:29:26 +0100 Subject: [PATCH 164/204] Testing: Changed ordering in tests to help prevent flaky test Think it would primariy use the created_at ordering based in the relation which could cause trouble in CI test environment. This better forces Id based ordering --- tests/Activity/WatchTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Activity/WatchTest.php b/tests/Activity/WatchTest.php index 8be09f890dc..dc78e894253 100644 --- a/tests/Activity/WatchTest.php +++ b/tests/Activity/WatchTest.php @@ -218,10 +218,10 @@ public function test_notify_comment_replies() $notifications = Notification::fake(); - $this->actingAs($editor)->post("/comment/{$entities['page']->id}", [ + $resp = $this->actingAs($editor)->post("/comment/{$entities['page']->id}", [ 'html' => '

    My new comment

    ' ]); - $comment = $entities['page']->comments()->orderBy('id', 'desc')->first(); + $comment = $entities['page']->comments()->reorder('id', 'desc')->first(); $this->asAdmin()->post("/comment/{$entities['page']->id}", [ 'html' => '

    My new comment response

    ', From 49aa0250121f177c1828cc8c984c4e04cfeeba35 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 19 May 2026 17:56:48 +0100 Subject: [PATCH 165/204] Page Editor: Started contents view in toolbox Added visual system, not yet added on-click logic. Related to #4218 --- lang/en/entities.php | 3 + resources/icons/contents.svg | 1 + resources/js/components/index.ts | 1 + resources/js/components/toolbox-contents.ts | 132 ++++++++++++++++++ resources/js/services/vdom.ts | 4 + resources/sass/_lists.scss | 10 ++ .../pages/parts/editor-toolbox.blade.php | 3 + .../show-sidebar-section-page-nav.blade.php | 4 +- .../pages/parts/toolbox-contents.blade.php | 9 ++ 9 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 resources/icons/contents.svg create mode 100644 resources/js/components/toolbox-contents.ts create mode 100644 resources/views/pages/parts/toolbox-contents.blade.php diff --git a/lang/en/entities.php b/lang/en/entities.php index 5501d2bc229..58c00ec4b27 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -331,6 +331,9 @@ // 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', diff --git a/resources/icons/contents.svg b/resources/icons/contents.svg new file mode 100644 index 00000000000..3a315b431cc --- /dev/null +++ b/resources/icons/contents.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/js/components/index.ts b/resources/js/components/index.ts index 736d93f0595..688877227f2 100644 --- a/resources/js/components/index.ts +++ b/resources/js/components/index.ts @@ -58,6 +58,7 @@ export {Tabs} from './tabs'; export {TagManager} from './tag-manager'; export {TemplateManager} from './template-manager'; export {ToggleSwitch} from './toggle-switch'; +export {ToolboxContents} from './toolbox-contents'; export {TriLayout} from './tri-layout'; export {UserSelect} from './user-select'; export {WebhookEvents} from './webhook-events'; diff --git a/resources/js/components/toolbox-contents.ts b/resources/js/components/toolbox-contents.ts new file mode 100644 index 00000000000..e09f2b5acf4 --- /dev/null +++ b/resources/js/components/toolbox-contents.ts @@ -0,0 +1,132 @@ +import {Component} from "./component"; +import {debounce} from "../services/util"; +import {EditorToolboxChangeEventData} from "./editor-toolbox"; +import {PageEditor} from "./page-editor"; +import {MarkdownEditor} from "./markdown-editor"; +import {WysiwygEditorTinymce} from "./wysiwyg-editor-tinymce"; +import {WysiwygEditor} from "./wysiwyg-editor"; +import {elem} from "../services/dom"; +import {patchDomFromDom} from "../services/vdom"; + +interface ToolboxContentHeader { + // The text shown for the header + text: string; + // The level/depth of the header + level: number; + // The index of the header relative to all other headers in the content + index: number; + // The id set for the header (if at all) + id: string; +} + +export class ToolboxContents extends Component { + protected container!: HTMLElement; + protected noneEl!: HTMLElement; + protected display!: HTMLElement; + protected isActive: boolean = false; + + setup() { + this.container = this.$el as HTMLLinkElement; + this.noneEl = this.$refs.none; + this.display = this.$refs.display; + + // Listen to when visible in the editor toolbox so we only update when visible + window.addEventListener('editor-toolbox-change', ((event: CustomEvent) => { + const tabName: string = event.detail.tab; + const isOpen = event.detail.open; + if (tabName === 'contents' && isOpen) { + if (this.isActive !== true) { + this.render(); + } + this.isActive = true; + } else { + this.isActive = false; + } + }) as EventListener); + + // Listen to content changes from the editor + const onContentChangeDebounced = debounce(this.onContentChange.bind(this), 500, false); + window.$events.listen('editor-html-change', onContentChangeDebounced); + window.$events.listen('editor-markdown-change', onContentChangeDebounced); + } + + protected onContentChange(): void { + if (!this.isActive) { + return; + } + this.render(); + } + + protected async render(): Promise { + const editorHtml = await this.getEditorHtml(); + const headers = this.parseHeadersFromHtml(editorHtml); + this.rebaseHeaders(headers); + + const headerDom = this.headersToDom(headers); + let displayChild = this.display.firstElementChild; + if (!displayChild) { + displayChild = document.createElement("ul"); + this.display.appendChild(displayChild); + } + patchDomFromDom(displayChild, headerDom); + + this.noneEl.hidden = headers.length > 0; + } + + protected async getEditorHtml(): Promise { + const pageEditorComponent = window.$components.first('page-editor') as PageEditor; + const editor = pageEditorComponent.getEditorComponent() as (MarkdownEditor|WysiwygEditorTinymce|WysiwygEditor); + return (await editor.getContent()).html; + } + + protected parseHeadersFromHtml(html: string): ToolboxContentHeader[] { + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + const headerNodes = doc.querySelectorAll('h1, h2, h3, h4, h5, h6'); + const headers: ToolboxContentHeader[] = []; + + for (let i = 0; i < headerNodes.length; i++) { + const headerNode = headerNodes[i]; + const level = Number(headerNode.tagName.replace('H', '')); + headers.push({ + text: headerNode.textContent, + level, + id: headerNode.id || '', + index: i, + }); + } + + return headers; + } + + protected rebaseHeaders(headers: ToolboxContentHeader[]): void { + if (headers.length === 0) { + return; + } + + do { + var minLevel = Math.min(...headers.map(h => h.level)); + if (minLevel > 1) { + for (const header of headers) { + header.level--; + } + } + } while (minLevel > 1); + } + + protected headersToDom(headers: ToolboxContentHeader[]): HTMLElement { + const headerItems = headers.map(header => { + return elem('li', { + 'data-level': String(header.level), + id: header.id, + class: `page-nav-item h${header.level}`, + }, [ + elem('a', {class: 'text-limit-lines-1 block', href: `#${header.id}`}, [header.text]), + elem('div', {class: 'link-background sidebar-page-nav-bullet'}), + ]); + }); + return elem('ul', { + class: 'sidebar-page-nav menu' + }, headerItems); + } +} \ No newline at end of file diff --git a/resources/js/services/vdom.ts b/resources/js/services/vdom.ts index c32f328f5e7..051b1a42abc 100644 --- a/resources/js/services/vdom.ts +++ b/resources/js/services/vdom.ts @@ -24,3 +24,7 @@ export function patchDomFromHtmlString(domTarget: Element, html: string): void { contentDom.innerHTML = html; getPatcher()(toVNode(domTarget), toVNode(contentDom)); } + +export function patchDomFromDom(domTarget: Element, domSource: Element): void { + getPatcher()(toVNode(domTarget), toVNode(domSource)); +} diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index d9a1195aa0d..249dca0d57e 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -93,6 +93,8 @@ list-style: none; @include mixins.margin(vars.$s, 0, vars.$m, vars.$xs); position: relative; + padding-inline: 0; + display: block; &:after { content: ''; display: block; @@ -153,6 +155,14 @@ } } } +.toolbox-tab-content .sidebar-page-nav { + .sidebar-page-nav-bullet { + box-shadow: 0 0 0 6px #FFFFFF; + } + li { + font-size: 1em; + } +} // Sidebar list .book-tree .sidebar-page-list { diff --git a/resources/views/pages/parts/editor-toolbox.blade.php b/resources/views/pages/parts/editor-toolbox.blade.php index 8b7a68a9aa7..8ef259b91e4 100644 --- a/resources/views/pages/parts/editor-toolbox.blade.php +++ b/resources/views/pages/parts/editor-toolbox.blade.php @@ -11,6 +11,7 @@ @if($comments->enabled()) @endif +
    @@ -37,4 +38,6 @@ @include('pages.parts.toolbox-comments') @endif + @include('pages.parts.toolbox-contents') +
    diff --git a/resources/views/pages/parts/show-sidebar-section-page-nav.blade.php b/resources/views/pages/parts/show-sidebar-section-page-nav.blade.php index 88db87e6483..d046a8ca141 100644 --- a/resources/views/pages/parts/show-sidebar-section-page-nav.blade.php +++ b/resources/views/pages/parts/show-sidebar-section-page-nav.blade.php @@ -2,14 +2,14 @@ @endif \ No newline at end of file diff --git a/resources/views/pages/parts/toolbox-contents.blade.php b/resources/views/pages/parts/toolbox-contents.blade.php new file mode 100644 index 00000000000..6234cdf4392 --- /dev/null +++ b/resources/views/pages/parts/toolbox-contents.blade.php @@ -0,0 +1,9 @@ +
    +

    {{ trans('entities.page_contents') }}

    + +
    +

    {{ trans('entities.page_contents_info') }}

    + +
    +
    +
    \ No newline at end of file From c58eb918939c9ef24998e3da9379c29969d75f0b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 20 May 2026 12:12:38 +0100 Subject: [PATCH 166/204] Page Editor: Added contents view click logic Added jump-to-header logic for lexical WYSIWYG, and both codemirror & plaintext markdown editor windows. --- resources/js/components/toolbox-contents.ts | 22 ++++++++++++- resources/js/markdown/actions.ts | 31 +++++++++++++++++-- resources/js/markdown/common-events.ts | 5 +++ resources/js/markdown/inputs/codemirror.ts | 13 ++++++++ resources/js/markdown/inputs/interface.ts | 6 ++++ resources/js/markdown/inputs/textarea.ts | 14 +++++++++ resources/js/services/vdom.ts | 2 ++ .../js/wysiwyg/services/common-events.ts | 6 +++- resources/js/wysiwyg/utils/actions.ts | 13 +++++++- 9 files changed, 107 insertions(+), 5 deletions(-) diff --git a/resources/js/components/toolbox-contents.ts b/resources/js/components/toolbox-contents.ts index e09f2b5acf4..569b574ee37 100644 --- a/resources/js/components/toolbox-contents.ts +++ b/resources/js/components/toolbox-contents.ts @@ -48,6 +48,24 @@ export class ToolboxContents extends Component { const onContentChangeDebounced = debounce(this.onContentChange.bind(this), 500, false); window.$events.listen('editor-html-change', onContentChangeDebounced); window.$events.listen('editor-markdown-change', onContentChangeDebounced); + + // Listen to header click + this.container.addEventListener('click', (event) => { + const header = (event.target as HTMLElement).closest('li[data-index]'); + if (header instanceof HTMLElement) { + event.preventDefault(); + this.onHeaderSelect(header); + } + }); + } + + protected onHeaderSelect(headerElement: HTMLElement): void { + const id = headerElement.getAttribute('data-id') || ''; + const index = Number(headerElement.getAttribute('data-index') || ''); + window.$events.emit('editor::focus-heading', { + index, + id, + }); } protected onContentChange(): void { @@ -118,7 +136,9 @@ export class ToolboxContents extends Component { const headerItems = headers.map(header => { return elem('li', { 'data-level': String(header.level), - id: header.id, + 'data-index': String(header.index), + 'data-id': header.id, + id: `page-contents-${header.id}`, class: `page-nav-item h${header.level}`, }, [ elem('a', {class: 'text-limit-lines-1 block', href: `#${header.id}`}, [header.text]), diff --git a/resources/js/markdown/actions.ts b/resources/js/markdown/actions.ts index 36d21ab1dc6..ff1f0bc9435 100644 --- a/resources/js/markdown/actions.ts +++ b/resources/js/markdown/actions.ts @@ -184,14 +184,41 @@ export class Actions { } } - focus() { + /** + * Set user focus on the editor edit area. + */ + focus(): void { this.editor.input.focus(); } + /** + * Focus on the text for a specific header in the content. + */ + focusOnHeader(index: number): void { + const headerPattern = /^\s{0,3}(#+| { + const isHeader = headerPattern.test(content); + if (isHeader && !inCodeBoundary) { + currentIndex++; + if (currentIndex === index) { + this.editor.input.setSelection({from: range.to, to: range.to}, true); + return false; + } + } else if (codeBoundary.test(content)) { + inCodeBoundary = !inCodeBoundary; + } + }); + + this.focus(); + } + /** * Insert content into the editor. */ - insertContent(content: string) { + insertContent(content: string): void { this.#replaceSelection(content, content.length); } diff --git a/resources/js/markdown/common-events.ts b/resources/js/markdown/common-events.ts index 4bfc4bb4619..e8cbe561c8e 100644 --- a/resources/js/markdown/common-events.ts +++ b/resources/js/markdown/common-events.ts @@ -1,4 +1,5 @@ import {MarkdownEditor} from "./index.mjs"; +import {focusOnHeader} from "../wysiwyg/utils/actions"; export interface HtmlOrMarkdown { html: string; @@ -33,4 +34,8 @@ export function listenToCommonEvents(editor: MarkdownEditor): void { window.$events.listen('editor::focus', () => { editor.actions.focus(); }); + + window.$events.listen<{id: string, index: number}>('editor::focus-heading', ({index}) => { + editor.actions.focusOnHeader(index); + }); } diff --git a/resources/js/markdown/inputs/codemirror.ts b/resources/js/markdown/inputs/codemirror.ts index 827068238e0..8cc7f409cc5 100644 --- a/resources/js/markdown/inputs/codemirror.ts +++ b/resources/js/markdown/inputs/codemirror.ts @@ -102,6 +102,19 @@ export class CodemirrorInput implements MarkdownEditorInput { return {from: line.from, to: line.to}; } + forEachLine(callback: (lineNum: number, content: string, range: MarkdownEditorInputSelection) => false | void): void { + const docText = this.cm.state.doc; + let lineCount = 0; + for (const line of docText.iterLines()) { + lineCount++; + const lineInfo = docText.line(lineCount); + const result = callback(lineCount, line, {from: lineInfo.from, to: lineInfo.to}); + if (result === false) { + break; + } + } + } + /** * Dispatch changes to the editor. */ diff --git a/resources/js/markdown/inputs/interface.ts b/resources/js/markdown/inputs/interface.ts index 1f7474a5088..d39f7dc1418 100644 --- a/resources/js/markdown/inputs/interface.ts +++ b/resources/js/markdown/inputs/interface.ts @@ -74,6 +74,12 @@ export interface MarkdownEditorInput { */ searchForLineContaining(text: string): MarkdownEditorInputSelection|null; + /** + * Run the provided callback against each line of content within the input. + * Callback can return false to stop further processing. + */ + forEachLine(callback: (lineNum: number, content: string, range: MarkdownEditorInputSelection) => false|void): void; + /** * Tear down the input. */ diff --git a/resources/js/markdown/inputs/textarea.ts b/resources/js/markdown/inputs/textarea.ts index e0c3ac37cf2..c232a24bf46 100644 --- a/resources/js/markdown/inputs/textarea.ts +++ b/resources/js/markdown/inputs/textarea.ts @@ -214,6 +214,20 @@ export class TextareaInput implements MarkdownEditorInput { return null; } + forEachLine(callback: (lineNum: number, content: string, range: MarkdownEditorInputSelection) => false | void): void { + const lines = this.getText().split('\n'); + let lineStart = 0; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const lineEnd = lineStart + line.length; + const result = callback(i + 1, line, {from: lineStart, to: lineEnd}); + if (result === false) { + break; + } + lineStart = lineEnd + 1; + } + } + setSelection(selection: MarkdownEditorInputSelection, scrollIntoView: boolean): void { this.input.selectionStart = selection.from; this.input.selectionEnd = selection.to; diff --git a/resources/js/services/vdom.ts b/resources/js/services/vdom.ts index 051b1a42abc..a83686778fe 100644 --- a/resources/js/services/vdom.ts +++ b/resources/js/services/vdom.ts @@ -1,6 +1,7 @@ import { init, attributesModule, + datasetModule, toVNode, } from 'snabbdom'; import {VNode} from "snabbdom/build/vnode"; @@ -14,6 +15,7 @@ function getPatcher(): vDomPatcher { patcher = init([ attributesModule, + datasetModule, ]); return patcher; diff --git a/resources/js/wysiwyg/services/common-events.ts b/resources/js/wysiwyg/services/common-events.ts index f7fc81cb3cf..9c6a91787cc 100644 --- a/resources/js/wysiwyg/services/common-events.ts +++ b/resources/js/wysiwyg/services/common-events.ts @@ -1,7 +1,7 @@ import {LexicalEditor} from "lexical"; import { appendHtmlToEditor, - focusEditor, + focusEditor, focusOnHeader, insertHtmlIntoEditor, prependHtmlToEditor, setEditorContentFromHtml @@ -41,6 +41,10 @@ export function listen(editor: LexicalEditor): void { focusEditor(editor); }); + window.$events.listen<{id: string, index: number}>('editor::focus-heading', ({index}) => { + focusOnHeader(editor, index); + }); + let changeFromLoading = true; editor.registerUpdateListener(({dirtyElements, dirtyLeaves, editorState, prevEditorState}) => { // Emit change event to component system (for draft detection) on actual user content change diff --git a/resources/js/wysiwyg/utils/actions.ts b/resources/js/wysiwyg/utils/actions.ts index 465d44d3136..f5b2db6aad4 100644 --- a/resources/js/wysiwyg/utils/actions.ts +++ b/resources/js/wysiwyg/utils/actions.ts @@ -1,6 +1,7 @@ import {$createParagraphNode, $getRoot, $getSelection, $insertNodes, $isBlockElementNode, LexicalEditor} from "lexical"; import {$generateHtmlFromNodes} from "@lexical/html"; -import {$getNearestNodeBlockParent, $htmlToBlockNodes, $htmlToNodes} from "./nodes"; +import {$getAllNodesOfType, $getNearestNodeBlockParent, $htmlToBlockNodes, $htmlToNodes} from "./nodes"; +import {$isHeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; export function setEditorContentFromHtml(editor: LexicalEditor, html: string) { editor.update(() => { @@ -101,4 +102,14 @@ export function focusEditor(editor: LexicalEditor): void { }); editor.commitUpdates(); editor.focus(() => {}, {defaultSelection: "rootStart"}); +} + +export function focusOnHeader(editor: LexicalEditor, headerIndex: number): void { + editor.update(() => { + const headers = $getAllNodesOfType($isHeadingNode); + const target = headers[headerIndex]; + if (target) { + target.selectStart(); + } + }); } \ No newline at end of file From 1b9ec75903ec3308b9840ac9948dce5ee3c913f4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 21 May 2026 09:48:20 +0100 Subject: [PATCH 167/204] Deps: Updated PHP package versions --- composer.lock | 387 ++++++++++++++++++++++++++------------------------ 1 file changed, 198 insertions(+), 189 deletions(-) diff --git a/composer.lock b/composer.lock index 4a56da48823..c0346a7b27c 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.379.8", + "version": "3.381.5", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "856ddf3d241c29132fe1eb946e112351ab043542" + "reference": "409208d62af0ddafbcb0af1a0bf514f5ffcaba92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/856ddf3d241c29132fe1eb946e112351ab043542", - "reference": "856ddf3d241c29132fe1eb946e112351ab043542", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/409208d62af0ddafbcb0af1a0bf514f5ffcaba92", + "reference": "409208d62af0ddafbcb0af1a0bf514f5ffcaba92", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.379.8" + "source": "https://github.com/aws/aws-sdk-php/tree/3.381.5" }, - "time": "2026-04-27T19:13:21+00:00" + "time": "2026-05-20T18:16:01+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1179,16 +1179,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.10.0", + "version": "7.10.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + "reference": "47ba23c7a55247e2e1b7407aca90e9bbed0d9d86" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/47ba23c7a55247e2e1b7407aca90e9bbed0d9d86", + "reference": "47ba23c7a55247e2e1b7407aca90e9bbed0d9d86", "shasum": "" }, "require": { @@ -1206,8 +1206,9 @@ "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", "guzzle/client-integration-tests": "3.0.2", + "guzzlehttp/test-server": "^0.3.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -1285,7 +1286,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + "source": "https://github.com/guzzle/guzzle/tree/7.10.3" }, "funding": [ { @@ -1301,20 +1302,20 @@ "type": "tidelift" } ], - "time": "2025-08-23T22:36:01+00:00" + "time": "2026-05-20T22:59:19+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.3.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "481557b130ef3790cf82b713667b43030dc9c957" + "reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", - "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "url": "https://api.github.com/repos/guzzle/promises/zipball/09e8a212562fb1fb6a512c4156ed71525969d6c2", + "reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2", "shasum": "" }, "require": { @@ -1322,7 +1323,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" + "phpunit/phpunit": "^8.5.52 || ^9.6.34" }, "type": "library", "extra": { @@ -1368,7 +1369,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.3.0" + "source": "https://github.com/guzzle/promises/tree/2.4.1" }, "funding": [ { @@ -1384,20 +1385,20 @@ "type": "tidelift" } ], - "time": "2025-08-22T14:34:08+00:00" + "time": "2026-05-20T22:57:30+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.9.0", + "version": "2.10.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" + "reference": "73ab136360b5dfd858006eae9795e8fe43c80361" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", - "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/73ab136360b5dfd858006eae9795e8fe43c80361", + "reference": "73ab136360b5dfd858006eae9795e8fe43c80361", "shasum": "" }, "require": { @@ -1412,9 +1413,9 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "0.9.0", + "http-interop/http-factory-tests": "1.1.0", "jshttp/mime-db": "1.54.0.1", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" + "phpunit/phpunit": "^8.5.52 || ^9.6.34" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1485,7 +1486,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.9.0" + "source": "https://github.com/guzzle/psr7/tree/2.10.1" }, "funding": [ { @@ -1501,7 +1502,7 @@ "type": "tidelift" } ], - "time": "2026-03-10T16:41:02+00:00" + "time": "2026-05-20T09:27:36+00:00" }, { "name": "guzzlehttp/uri-template", @@ -1659,16 +1660,16 @@ }, { "name": "intervention/image", - "version": "3.11.7", + "version": "3.11.8", "source": { "type": "git", "url": "https://github.com/Intervention/image.git", - "reference": "2159bcccff18f09d2a392679b81a82c5a003f9bb" + "reference": "cf04c8dd245697f701057c13d4bfe140d584e738" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/2159bcccff18f09d2a392679b81a82c5a003f9bb", - "reference": "2159bcccff18f09d2a392679b81a82c5a003f9bb", + "url": "https://api.github.com/repos/Intervention/image/zipball/cf04c8dd245697f701057c13d4bfe140d584e738", + "reference": "cf04c8dd245697f701057c13d4bfe140d584e738", "shasum": "" }, "require": { @@ -1681,7 +1682,7 @@ "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0", "slevomat/coding-standard": "~8.0", - "squizlabs/php_codesniffer": "^3.8" + "squizlabs/php_codesniffer": "^4" }, "suggest": { "ext-exif": "Recommended to be able to read EXIF data properly." @@ -1715,7 +1716,7 @@ ], "support": { "issues": "https://github.com/Intervention/image/issues", - "source": "https://github.com/Intervention/image/tree/3.11.7" + "source": "https://github.com/Intervention/image/tree/3.11.8" }, "funding": [ { @@ -1731,20 +1732,20 @@ "type": "ko_fi" } ], - "time": "2026-02-19T13:11:17+00:00" + "time": "2026-05-01T08:20:10+00:00" }, { "name": "knplabs/knp-snappy", - "version": "v1.6.0", + "version": "v1.7.2", "source": { "type": "git", "url": "https://github.com/KnpLabs/snappy.git", - "reference": "af73003db677563fa982b50c1aec4d1e2b2f30b2" + "reference": "1461239a8b265fcc5457b7bdeb842c75b0f066eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/KnpLabs/snappy/zipball/af73003db677563fa982b50c1aec4d1e2b2f30b2", - "reference": "af73003db677563fa982b50c1aec4d1e2b2f30b2", + "url": "https://api.github.com/repos/KnpLabs/snappy/zipball/1461239a8b265fcc5457b7bdeb842c75b0f066eb", + "reference": "1461239a8b265fcc5457b7bdeb842c75b0f066eb", "shasum": "" }, "require": { @@ -1755,8 +1756,8 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.0", "pedrotroller/php-cs-custom-fixer": "^2.19", - "phpstan/phpstan": "^1.0.0", - "phpstan/phpstan-phpunit": "^1.0.0", + "phpstan/phpstan": "^2.1.39", + "phpstan/phpstan-phpunit": "^2.0.15", "phpunit/phpunit": "^9.6.29" }, "type": "library", @@ -1796,22 +1797,22 @@ ], "support": { "issues": "https://github.com/KnpLabs/snappy/issues", - "source": "https://github.com/KnpLabs/snappy/tree/v1.6.0" + "source": "https://github.com/KnpLabs/snappy/tree/v1.7.2" }, - "time": "2026-02-13T12:50:40+00:00" + "time": "2026-05-15T15:04:49+00:00" }, { "name": "laravel/framework", - "version": "v12.58.0", + "version": "v12.60.2", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "6172ae1f44ba5d89e111057ee4a4e7c27f5a610d" + "reference": "b8b55ce32175cc00f834a56eeb6316f18ed6ea39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/6172ae1f44ba5d89e111057ee4a4e7c27f5a610d", - "reference": "6172ae1f44ba5d89e111057ee4a4e7c27f5a610d", + "url": "https://api.github.com/repos/laravel/framework/zipball/b8b55ce32175cc00f834a56eeb6316f18ed6ea39", + "reference": "b8b55ce32175cc00f834a56eeb6316f18ed6ea39", "shasum": "" }, "require": { @@ -2020,20 +2021,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-04-26T16:42:04+00:00" + "time": "2026-05-20T11:48:19+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.17", + "version": "v0.3.18", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "6a82ac19a28b916ae0885828795dbd4c59d9a818" + "reference": "a19af51bb144bf87f08397921fa619f85c7d4e72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/6a82ac19a28b916ae0885828795dbd4c59d9a818", - "reference": "6a82ac19a28b916ae0885828795dbd4c59d9a818", + "url": "https://api.github.com/repos/laravel/prompts/zipball/a19af51bb144bf87f08397921fa619f85c7d4e72", + "reference": "a19af51bb144bf87f08397921fa619f85c7d4e72", "shasum": "" }, "require": { @@ -2077,9 +2078,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.17" + "source": "https://github.com/laravel/prompts/tree/v0.3.18" }, - "time": "2026-04-20T16:07:33+00:00" + "time": "2026-05-19T00:47:18+00:00" }, { "name": "laravel/serializable-closure", @@ -2471,16 +2472,16 @@ }, { "name": "league/flysystem", - "version": "3.33.0", + "version": "3.34.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "570b8871e0ce693764434b29154c54b434905350" + "reference": "2daaac3b0d4c83ea7ed5d8586e786f5d00f3540e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/570b8871e0ce693764434b29154c54b434905350", - "reference": "570b8871e0ce693764434b29154c54b434905350", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2daaac3b0d4c83ea7ed5d8586e786f5d00f3540e", + "reference": "2daaac3b0d4c83ea7ed5d8586e786f5d00f3540e", "shasum": "" }, "require": { @@ -2548,26 +2549,26 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.33.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.34.0" }, - "time": "2026-03-25T07:59:30+00:00" + "time": "2026-05-14T10:28:08+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.32.0", + "version": "3.34.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "a1979df7c9784d334ea6df356aed3d18ac6673d0" + "reference": "0c62fdac907791d8649ad3c61cb7a77628344fb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/a1979df7c9784d334ea6df356aed3d18ac6673d0", - "reference": "a1979df7c9784d334ea6df356aed3d18ac6673d0", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/0c62fdac907791d8649ad3c61cb7a77628344fb8", + "reference": "0c62fdac907791d8649ad3c61cb7a77628344fb8", "shasum": "" }, "require": { - "aws/aws-sdk-php": "^3.295.10", + "aws/aws-sdk-php": "^3.371.5", "league/flysystem": "^3.10.0", "league/mime-type-detection": "^1.0.0", "php": "^8.0.2" @@ -2603,9 +2604,9 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.32.0" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.34.0" }, - "time": "2026-02-25T16:46:44+00:00" + "time": "2026-05-04T08:24:00+00:00" }, { "name": "league/flysystem-local", @@ -3534,16 +3535,16 @@ }, { "name": "nette/utils", - "version": "v4.1.3", + "version": "v4.1.4", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" + "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", - "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", + "url": "https://api.github.com/repos/nette/utils/zipball/7da6c396d7ebe142bc857c20479d5e70a5e1aac7", + "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7", "shasum": "" }, "require": { @@ -3619,9 +3620,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.3" + "source": "https://github.com/nette/utils/tree/v4.1.4" }, - "time": "2026-02-13T03:05:33+00:00" + "time": "2026-05-11T20:49:54+00:00" }, { "name": "nikic/php-parser", @@ -3770,21 +3771,21 @@ }, { "name": "onelogin/php-saml", - "version": "4.3.1", + "version": "4.3.2", "source": { "type": "git", "url": "https://github.com/SAML-Toolkits/php-saml.git", - "reference": "b009f160e4ac11f49366a45e0d45706b48429353" + "reference": "26b3a47349415e5b7aa300ba4ab7fc316c65f19e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/b009f160e4ac11f49366a45e0d45706b48429353", - "reference": "b009f160e4ac11f49366a45e0d45706b48429353", + "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/26b3a47349415e5b7aa300ba4ab7fc316c65f19e", + "reference": "26b3a47349415e5b7aa300ba4ab7fc316c65f19e", "shasum": "" }, "require": { "php": ">=7.3", - "robrichards/xmlseclibs": ">=3.1.4" + "robrichards/xmlseclibs": "^3.1.5" }, "require-dev": { "pdepend/pdepend": "^2.8.0", @@ -3830,7 +3831,7 @@ "type": "github" } ], - "time": "2025-12-09T10:50:49+00:00" + "time": "2026-05-07T22:38:04+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -5510,16 +5511,16 @@ }, { "name": "symfony/console", - "version": "v7.4.8", + "version": "v7.4.11", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707" + "reference": "ed0107e43ab452aa77ae99e005b95e56b556e075" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707", - "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707", + "url": "https://api.github.com/repos/symfony/console/zipball/ed0107e43ab452aa77ae99e005b95e56b556e075", + "reference": "ed0107e43ab452aa77ae99e005b95e56b556e075", "shasum": "" }, "require": { @@ -5584,7 +5585,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.8" + "source": "https://github.com/symfony/console/tree/v7.4.11" }, "funding": [ { @@ -5604,20 +5605,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T13:54:39+00:00" + "time": "2026-05-13T12:04:42+00:00" }, { "name": "symfony/css-selector", - "version": "v7.4.8", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "b055f228a4178a1d6774909903905e3475f3eac8" + "reference": "b75663ed96cf4756e28e3105476f220f92886cc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/b055f228a4178a1d6774909903905e3475f3eac8", - "reference": "b055f228a4178a1d6774909903905e3475f3eac8", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/b75663ed96cf4756e28e3105476f220f92886cc4", + "reference": "b75663ed96cf4756e28e3105476f220f92886cc4", "shasum": "" }, "require": { @@ -5653,7 +5654,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.8" + "source": "https://github.com/symfony/css-selector/tree/v7.4.9" }, "funding": [ { @@ -5673,20 +5674,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-04-18T13:18:21+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", "shasum": "" }, "require": { @@ -5699,7 +5700,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -5724,7 +5725,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" }, "funding": [ { @@ -5735,12 +5736,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-04-13T15:52:40+00:00" }, { "name": "symfony/error-handler", @@ -5826,16 +5831,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v7.4.8", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "f57b899fa736fd71121168ef268f23c206083f0a" + "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f57b899fa736fd71121168ef268f23c206083f0a", - "reference": "f57b899fa736fd71121168ef268f23c206083f0a", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e4a2e29753c7801f7a8340e066cfa788f3bc8101", + "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101", "shasum": "" }, "require": { @@ -5887,7 +5892,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.9" }, "funding": [ { @@ -5907,20 +5912,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T13:54:39+00:00" + "time": "2026-04-18T13:18:21+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", "shasum": "" }, "require": { @@ -5934,7 +5939,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -5967,7 +5972,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0" }, "funding": [ { @@ -5978,25 +5983,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-01-05T13:30:16+00:00" }, { "name": "symfony/filesystem", - "version": "v7.4.8", + "version": "v7.4.11", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "58b9790d12f9670b7f53a1c1738febd3108970a5" + "reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/58b9790d12f9670b7f53a1c1738febd3108970a5", - "reference": "58b9790d12f9670b7f53a1c1738febd3108970a5", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d721ea61b4a5fba8c5b6e7c1feda19efea144b50", + "reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50", "shasum": "" }, "require": { @@ -6033,7 +6042,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.8" + "source": "https://github.com/symfony/filesystem/tree/v7.4.11" }, "funding": [ { @@ -6053,7 +6062,7 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-11T16:38:44+00:00" }, { "name": "symfony/finder", @@ -6207,16 +6216,16 @@ }, { "name": "symfony/http-kernel", - "version": "v7.4.8", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "017e76ad089bac281553389269e259e155935e1a" + "reference": "7922b53e70d2ba2027af8bb6a59d91eb3541ea4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/017e76ad089bac281553389269e259e155935e1a", - "reference": "017e76ad089bac281553389269e259e155935e1a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/7922b53e70d2ba2027af8bb6a59d91eb3541ea4d", + "reference": "7922b53e70d2ba2027af8bb6a59d91eb3541ea4d", "shasum": "" }, "require": { @@ -6302,7 +6311,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.8" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.12" }, "funding": [ { @@ -6322,20 +6331,20 @@ "type": "tidelift" } ], - "time": "2026-03-31T20:57:01+00:00" + "time": "2026-05-20T09:27:11+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.8", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "f6ea532250b476bfc1b56699b388a1bdbf168f62" + "reference": "5cefb712a25f320579615ba9e1942abaeade7dff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/f6ea532250b476bfc1b56699b388a1bdbf168f62", - "reference": "f6ea532250b476bfc1b56699b388a1bdbf168f62", + "url": "https://api.github.com/repos/symfony/mailer/zipball/5cefb712a25f320579615ba9e1942abaeade7dff", + "reference": "5cefb712a25f320579615ba9e1942abaeade7dff", "shasum": "" }, "require": { @@ -6386,7 +6395,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.8" + "source": "https://github.com/symfony/mailer/tree/v7.4.12" }, "funding": [ { @@ -6406,20 +6415,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symfony/mime", - "version": "v7.4.8", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "6df02f99998081032da3407a8d6c4e1dcb5d4379" + "reference": "b198dd66c211c97119bcaaff7c13431dbbb5e470" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/6df02f99998081032da3407a8d6c4e1dcb5d4379", - "reference": "6df02f99998081032da3407a8d6c4e1dcb5d4379", + "url": "https://api.github.com/repos/symfony/mime/zipball/b198dd66c211c97119bcaaff7c13431dbbb5e470", + "reference": "b198dd66c211c97119bcaaff7c13431dbbb5e470", "shasum": "" }, "require": { @@ -6475,7 +6484,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.8" + "source": "https://github.com/symfony/mime/tree/v7.4.12" }, "funding": [ { @@ -6495,7 +6504,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T14:11:46+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symfony/polyfill-ctype", @@ -7328,16 +7337,16 @@ }, { "name": "symfony/process", - "version": "v7.4.8", + "version": "v7.4.11", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "60f19cd3badc8de688421e21e4305eba50f8089a" + "reference": "d9593c9efa40499eb078b81144de42cbc28a31f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/60f19cd3badc8de688421e21e4305eba50f8089a", - "reference": "60f19cd3badc8de688421e21e4305eba50f8089a", + "url": "https://api.github.com/repos/symfony/process/zipball/d9593c9efa40499eb078b81144de42cbc28a31f0", + "reference": "d9593c9efa40499eb078b81144de42cbc28a31f0", "shasum": "" }, "require": { @@ -7369,7 +7378,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.8" + "source": "https://github.com/symfony/process/tree/v7.4.11" }, "funding": [ { @@ -7389,20 +7398,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-11T16:55:21+00:00" }, { "name": "symfony/routing", - "version": "v7.4.8", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b" + "reference": "3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b", - "reference": "9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b", + "url": "https://api.github.com/repos/symfony/routing/zipball/3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204", + "reference": "3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204", "shasum": "" }, "require": { @@ -7454,7 +7463,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.8" + "source": "https://github.com/symfony/routing/tree/v7.4.12" }, "funding": [ { @@ -7474,20 +7483,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", "shasum": "" }, "require": { @@ -7505,7 +7514,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -7541,7 +7550,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" }, "funding": [ { @@ -7561,20 +7570,20 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-03-28T09:44:51+00:00" }, { "name": "symfony/string", - "version": "v7.4.8", + "version": "v7.4.11", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "114ac57257d75df748eda23dd003878080b8e688" + "reference": "965f7306a43383d02c6aca1e3f3bd2f0ea5dee15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/114ac57257d75df748eda23dd003878080b8e688", - "reference": "114ac57257d75df748eda23dd003878080b8e688", + "url": "https://api.github.com/repos/symfony/string/zipball/965f7306a43383d02c6aca1e3f3bd2f0ea5dee15", + "reference": "965f7306a43383d02c6aca1e3f3bd2f0ea5dee15", "shasum": "" }, "require": { @@ -7632,7 +7641,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.8" + "source": "https://github.com/symfony/string/tree/v7.4.11" }, "funding": [ { @@ -7652,20 +7661,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-13T12:04:42+00:00" }, { "name": "symfony/translation", - "version": "v7.4.8", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "33600f8489485425bfcddd0d983391038d3422e7" + "reference": "ada7578c30dd5feaa8259cff3e885069ea81ddde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/33600f8489485425bfcddd0d983391038d3422e7", - "reference": "33600f8489485425bfcddd0d983391038d3422e7", + "url": "https://api.github.com/repos/symfony/translation/zipball/ada7578c30dd5feaa8259cff3e885069ea81ddde", + "reference": "ada7578c30dd5feaa8259cff3e885069ea81ddde", "shasum": "" }, "require": { @@ -7732,7 +7741,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.8" + "source": "https://github.com/symfony/translation/tree/v7.4.10" }, "funding": [ { @@ -7752,20 +7761,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-06T11:19:24+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d", "shasum": "" }, "require": { @@ -7778,7 +7787,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -7814,7 +7823,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.0" }, "funding": [ { @@ -7834,20 +7843,20 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2026-01-05T13:30:16+00:00" }, { "name": "symfony/uid", - "version": "v7.4.8", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "6883ebdf7bf6a12b37519dbc0df62b0222401b56" + "reference": "2676b524340abcfe4d6151ec698463cebafee439" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/6883ebdf7bf6a12b37519dbc0df62b0222401b56", - "reference": "6883ebdf7bf6a12b37519dbc0df62b0222401b56", + "url": "https://api.github.com/repos/symfony/uid/zipball/2676b524340abcfe4d6151ec698463cebafee439", + "reference": "2676b524340abcfe4d6151ec698463cebafee439", "shasum": "" }, "require": { @@ -7892,7 +7901,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.4.8" + "source": "https://github.com/symfony/uid/tree/v7.4.9" }, "funding": [ { @@ -7912,7 +7921,7 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-04-30T15:19:22+00:00" }, { "name": "symfony/var-dumper", @@ -9170,11 +9179,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.54", + "version": "2.1.55", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8be50c3992107dc837b17da4d140fbbdf9a5c5bd", - "reference": "8be50c3992107dc837b17da4d140fbbdf9a5c5bd", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9eaac3826ed5e9b8427350a43cac825eeca3f566", + "reference": "9eaac3826ed5e9b8427350a43cac825eeca3f566", "shasum": "" }, "require": { @@ -9219,7 +9228,7 @@ "type": "github" } ], - "time": "2026-04-29T13:31:09+00:00" + "time": "2026-05-18T11:57:34+00:00" }, { "name": "phpunit/php-code-coverage", @@ -10842,16 +10851,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v7.4.8", + "version": "v7.4.12", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "2918e7c2ba964defca1f5b69c6f74886529e2dc8" + "reference": "b59b59122690976550fd142c23fab62c84738db6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2918e7c2ba964defca1f5b69c6f74886529e2dc8", - "reference": "2918e7c2ba964defca1f5b69c6f74886529e2dc8", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b59b59122690976550fd142c23fab62c84738db6", + "reference": "b59b59122690976550fd142c23fab62c84738db6", "shasum": "" }, "require": { @@ -10890,7 +10899,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.4.8" + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.12" }, "funding": [ { @@ -10910,7 +10919,7 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-20T07:20:23+00:00" }, { "name": "theseer/tokenizer", From ef821192267f075996efc9b98eabfb2fbb3ff4ad Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 21 May 2026 12:27:38 +0100 Subject: [PATCH 168/204] MFA: Added verify attempt rate limiting --- .../Controllers/HandlesPartialLogins.php | 6 ++ .../Controllers/MfaBackupCodesController.php | 13 ++++ app/Access/Controllers/MfaTotpController.php | 11 +++- app/Access/LoginService.php | 2 +- app/Access/Mfa/MfaVerificationLimiter.php | 62 +++++++++++++++++++ lang/en/auth.php | 1 + tests/Auth/MfaVerificationTest.php | 42 +++++++++++++ 7 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 app/Access/Mfa/MfaVerificationLimiter.php diff --git a/app/Access/Controllers/HandlesPartialLogins.php b/app/Access/Controllers/HandlesPartialLogins.php index 47a63d19b0c..8afad2776d1 100644 --- a/app/Access/Controllers/HandlesPartialLogins.php +++ b/app/Access/Controllers/HandlesPartialLogins.php @@ -22,4 +22,10 @@ protected function currentOrLastAttemptedUser(): User return $user; } + + protected function clearLastAttemptedUser(): void + { + $loginService = app()->make(LoginService::class); + $loginService->clearLastLoginAttempted(); + } } diff --git a/app/Access/Controllers/MfaBackupCodesController.php b/app/Access/Controllers/MfaBackupCodesController.php index 5c334674e7d..6ab3e1d1466 100644 --- a/app/Access/Controllers/MfaBackupCodesController.php +++ b/app/Access/Controllers/MfaBackupCodesController.php @@ -6,6 +6,7 @@ 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; @@ -19,6 +20,11 @@ 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. */ @@ -71,6 +77,12 @@ public function confirm() 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, [ @@ -89,6 +101,7 @@ function ($attribute, $value, $fail) use ($codeService, $codes) { $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')); diff --git a/app/Access/Controllers/MfaTotpController.php b/app/Access/Controllers/MfaTotpController.php index 5202fedc04f..b8a33322857 100644 --- a/app/Access/Controllers/MfaTotpController.php +++ b/app/Access/Controllers/MfaTotpController.php @@ -5,6 +5,7 @@ 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; @@ -20,7 +21,8 @@ class MfaTotpController extends Controller protected const SETUP_SECRET_SESSION_KEY = 'mfa-setup-totp-secret'; public function __construct( - protected TotpService $totp + protected TotpService $totp, + protected MfaVerificationLimiter $limiter, ) { } @@ -86,6 +88,12 @@ public function confirm(Request $request) 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, [ @@ -98,6 +106,7 @@ public function verify(Request $request, LoginService $loginService, MfaSession $mfaSession->markVerifiedForUser($user); $loginService->reattemptLoginFor($user); + $this->limiter->decrementAttempts($user, $request); return redirect()->intended(); } diff --git a/app/Access/LoginService.php b/app/Access/LoginService.php index c81e955722c..f089f5ba92c 100644 --- a/app/Access/LoginService.php +++ b/app/Access/LoginService.php @@ -126,7 +126,7 @@ protected function setLastLoginAttemptedForUser(User $user, string $method, bool /** * Clear the last login attempted session value. */ - protected function clearLastLoginAttempted(): void + public function clearLastLoginAttempted(): void { session()->remove(self::LAST_LOGIN_ATTEMPTED_SESSION_KEY); } diff --git a/app/Access/Mfa/MfaVerificationLimiter.php b/app/Access/Mfa/MfaVerificationLimiter.php new file mode 100644 index 00000000000..3ff0adad43d --- /dev/null +++ b/app/Access/Mfa/MfaVerificationLimiter.php @@ -0,0 +1,62 @@ + 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()}"; + } +} diff --git a/lang/en/auth.php b/lang/en/auth.php index 57f0cb5c632..47be4ea721e 100644 --- a/lang/en/auth.php +++ b/lang/en/auth.php @@ -8,6 +8,7 @@ 'failed' => 'These credentials do not match our records.', 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Sign up', diff --git a/tests/Auth/MfaVerificationTest.php b/tests/Auth/MfaVerificationTest.php index 76c59bc748b..967be1c6a61 100644 --- a/tests/Auth/MfaVerificationTest.php +++ b/tests/Auth/MfaVerificationTest.php @@ -66,6 +66,27 @@ public function test_totp_form_has_autofill_configured() $html->assertElementExists('input[autocomplete="one-time-code"][name="code"]'); } + public function test_totp_verification_is_rate_limited() + { + [$user, $secret, $loginResp] = $this->startTotpLogin(); + $loginService = $this->app->make(LoginService::class); + + $resp = $this->get('/mfa/verify'); + for ($i = 0; $i < 5; $i++) { + $this->post('/mfa/totp/verify', [ + 'code' => '123456', + ])->assertRedirect('/mfa/verify'); + $this->assertNotNull($loginService->getLastLoginAttemptUser()); + } + + $resp = $this->post('/mfa/totp/verify', [ + 'code' => '123456', + ]); + $resp->assertRedirect('/login'); + $this->assertSessionError('Too many multi-factor verification attempts. Please try again in 60 seconds.'); + $this->assertNull($loginService->getLastLoginAttemptUser()); + } + public function test_backup_code_verification() { [$user, $codes, $loginResp] = $this->startBackupCodeLogin(); @@ -147,6 +168,27 @@ public function test_backup_code_verification_shows_warning_when_limited_codes_r $resp->assertSeeText('You have less than 5 backup codes remaining, Please generate and store a new set before you run out of codes to prevent being locked out of your account.'); } + public function test_backup_code_verification_is_rate_limited() + { + [$user, $codes, $loginResp] = $this->startBackupCodeLogin(['abc12-def45', 'abc12-def46']); + $loginService = $this->app->make(LoginService::class); + + $resp = $this->get('/mfa/verify'); + for ($i = 0; $i < 5; $i++) { + $this->post('/mfa/backup_codes/verify', [ + 'code' => '123456abcd', + ])->assertRedirect('/mfa/verify'); + $this->assertNotNull($loginService->getLastLoginAttemptUser()); + } + + $resp = $this->post('/mfa/backup_codes/verify', [ + 'code' => '123456abcd', + ]); + $resp->assertRedirect('/login'); + $this->assertSessionError('Too many multi-factor verification attempts. Please try again in 60 seconds.'); + $this->assertNull($loginService->getLastLoginAttemptUser()); + } + public function test_backup_code_form_has_autofill_configured() { [$user, $codes, $loginResp] = $this->startBackupCodeLogin(); From d7ba0dc43082e09bd253bc92998c47293b0b1502 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 26 May 2026 12:50:19 +0100 Subject: [PATCH 169/204] CSP: Renamed CSS CSP option --- .env.example.complete | 4 ++-- app/Config/app.php | 4 ++-- app/Util/CspService.php | 2 +- phpunit.xml | 2 +- tests/SecurityHeaderTest.php | 8 ++++---- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.env.example.complete b/.env.example.complete index ed0cac5f473..6c773f601f1 100644 --- a/.env.example.complete +++ b/.env.example.complete @@ -398,8 +398,8 @@ ALLOWED_IFRAME_SOURCES="https://*.draw.io https://*.youtube.com https://*.youtub # 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_CSS_SOURCES="https://fonts.googleapis.com" -ALLOWED_CSS_SOURCES=null +# 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 diff --git a/app/Config/app.php b/app/Config/app.php index 12f285ea9e1..c38cd0e1f43 100644 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -72,10 +72,10 @@ // 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 sources/hostnames that can be loaded as CSS styles within BookStack. + // 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. - 'css_sources' => env('ALLOWED_CSS_SOURCES', null), + '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 diff --git a/app/Util/CspService.php b/app/Util/CspService.php index 68c544ff09d..9b871e7ff3b 100644 --- a/app/Util/CspService.php +++ b/app/Util/CspService.php @@ -169,7 +169,7 @@ protected function getAllowedIframeSources(): array */ protected function getAllowedStyleSources(): array { - $configured = config('app.css_sources'); + $configured = config('app.style_sources'); if (is_string($configured)) { $sources = array_filter(explode(' ', $configured)); diff --git a/phpunit.xml b/phpunit.xml index 52c5e7de8e9..55d84d129e6 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -18,7 +18,7 @@ - + diff --git a/tests/SecurityHeaderTest.php b/tests/SecurityHeaderTest.php index 8badc39b9b3..5664084ff15 100644 --- a/tests/SecurityHeaderTest.php +++ b/tests/SecurityHeaderTest.php @@ -153,7 +153,7 @@ public function test_frame_src_csp_header_drawio_host_includes_port_if_existing( public function test_style_src_csp_header_set_to_permissive_defaults_when_not_configured() { - config()->set('app.css_sources', null); + config()->set('app.style_sources', null); $resp = $this->get('/'); $header = $this->getCspHeader($resp, 'style-src'); @@ -162,7 +162,7 @@ public function test_style_src_csp_header_set_to_permissive_defaults_when_not_co public function test_style_src_csp_header_can_be_overridden_by_config() { - config()->set('app.css_sources', 'https://fonts.example.com'); + config()->set('app.style_sources', 'https://fonts.example.com'); $resp = $this->get('/'); $header = $this->getCspHeader($resp, 'style-src'); @@ -172,7 +172,7 @@ public function test_style_src_csp_header_can_be_overridden_by_config() public function test_style_src_csp_header_unsafe_inline_value_will_be_auto_quoted() { - config()->set('app.css_sources', 'unsafe-inline https://css.example.com'); + config()->set('app.style_sources', 'unsafe-inline https://css.example.com'); $resp = $this->get('/'); $header = $this->getCspHeader($resp, 'style-src'); @@ -182,7 +182,7 @@ public function test_style_src_csp_header_unsafe_inline_value_will_be_auto_quote public function test_style_src_can_be_blank_to_set_no_additions() { - config()->set('app.css_sources', ''); + config()->set('app.style_sources', ''); $resp = $this->get('/'); $header = $this->getCspHeader($resp, 'style-src'); From 99e405f80f4be5c3b6cc89afe4a1fd5c4c0cd385 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 26 May 2026 12:58:48 +0100 Subject: [PATCH 170/204] Page Editor: Added contents click handling for TinyMCE editor --- resources/js/wysiwyg-tinymce/common-events.js | 7 +++++++ resources/js/wysiwyg-tinymce/scrolling.js | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/resources/js/wysiwyg-tinymce/common-events.js b/resources/js/wysiwyg-tinymce/common-events.js index d0a5acdc24d..6f3d34f1f8e 100644 --- a/resources/js/wysiwyg-tinymce/common-events.js +++ b/resources/js/wysiwyg-tinymce/common-events.js @@ -1,3 +1,5 @@ +import {scrollToHeader} from "./scrolling"; + /** * @param {Editor} editor */ @@ -30,4 +32,9 @@ export function listen(editor) { editor.focus(); } }); + + // Focus on a specific heading + window.$events.listen('editor::focus-heading', ({index}) => { + scrollToHeader(editor, index); + }); } diff --git a/resources/js/wysiwyg-tinymce/scrolling.js b/resources/js/wysiwyg-tinymce/scrolling.js index 92f8f158323..b4f07984ea6 100644 --- a/resources/js/wysiwyg-tinymce/scrolling.js +++ b/resources/js/wysiwyg-tinymce/scrolling.js @@ -15,6 +15,23 @@ function scrollToText(editor, scrollId) { editor.focus(); } +/** + * Scroll to a specific header of the given index, relative to all headers in the content. + * @param {Editor} editor + * @param {Number} index + */ +export function scrollToHeader(editor, index) { + const headers = editor.dom.select('h1, h2, h3, h4, h5, h6'); + const targetHeader = headers[index]; + + if (targetHeader) { + targetHeader.scrollIntoView(); + editor.selection.select(targetHeader, true); + editor.selection.collapse(false); + editor.focus(); + } +} + /** * Scroll to a section dictated by the current URL query string, if present. * Used when directly editing a specific section of the page. From 0de1196b62efd626066dd893099edab35604a158 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 26 May 2026 13:27:48 +0100 Subject: [PATCH 171/204] Page Editor: Minor fixes - Removed unused import - Added some trailing newlines to code files - Prevented
    s confusing logic in MD editor - Aligned logic to select end of header across editors --- resources/js/components/toolbox-contents.ts | 2 +- resources/js/markdown/actions.ts | 2 +- resources/js/markdown/common-events.ts | 1 - resources/js/wysiwyg/utils/actions.ts | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/resources/js/components/toolbox-contents.ts b/resources/js/components/toolbox-contents.ts index 569b574ee37..1cbaeda5dda 100644 --- a/resources/js/components/toolbox-contents.ts +++ b/resources/js/components/toolbox-contents.ts @@ -149,4 +149,4 @@ export class ToolboxContents extends Component { class: 'sidebar-page-nav menu' }, headerItems); } -} \ No newline at end of file +} diff --git a/resources/js/markdown/actions.ts b/resources/js/markdown/actions.ts index ff1f0bc9435..2fd02c8c8d3 100644 --- a/resources/js/markdown/actions.ts +++ b/resources/js/markdown/actions.ts @@ -195,7 +195,7 @@ export class Actions { * Focus on the text for a specific header in the content. */ focusOnHeader(index: number): void { - const headerPattern = /^\s{0,3}(#+|])/i; const codeBoundary = /^\s{0,3}```/i; let currentIndex = -1; let inCodeBoundary = false; diff --git a/resources/js/markdown/common-events.ts b/resources/js/markdown/common-events.ts index e8cbe561c8e..e24f39dab8a 100644 --- a/resources/js/markdown/common-events.ts +++ b/resources/js/markdown/common-events.ts @@ -1,5 +1,4 @@ import {MarkdownEditor} from "./index.mjs"; -import {focusOnHeader} from "../wysiwyg/utils/actions"; export interface HtmlOrMarkdown { html: string; diff --git a/resources/js/wysiwyg/utils/actions.ts b/resources/js/wysiwyg/utils/actions.ts index f5b2db6aad4..c1d819f2226 100644 --- a/resources/js/wysiwyg/utils/actions.ts +++ b/resources/js/wysiwyg/utils/actions.ts @@ -109,7 +109,7 @@ export function focusOnHeader(editor: LexicalEditor, headerIndex: number): void const headers = $getAllNodesOfType($isHeadingNode); const target = headers[headerIndex]; if (target) { - target.selectStart(); + target.selectEnd(); } }); -} \ No newline at end of file +} From c26b66887fc70e878c3aad74a9ab8db6217a9d89 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 28 May 2026 11:29:39 +0200 Subject: [PATCH 172/204] Updated translations with changes from Crowdin (#6139) ## Details ## Checklist - [ ] 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. Co-authored-by: Crowdin Bot Reviewed-on: https://codeberg.org/bookstack/bookstack/pulls/6139 --- lang/ar/activities.php | 2 + lang/ar/auth.php | 1 + lang/ar/entities.php | 3 + lang/ar/settings.php | 4 + lang/bg/activities.php | 2 + lang/bg/auth.php | 1 + lang/bg/entities.php | 3 + lang/bg/settings.php | 4 + lang/bn/activities.php | 2 + lang/bn/auth.php | 1 + lang/bn/entities.php | 3 + lang/bn/settings.php | 4 + lang/bs/activities.php | 2 + lang/bs/auth.php | 1 + lang/bs/entities.php | 3 + lang/bs/settings.php | 4 + lang/ca/activities.php | 2 + lang/ca/auth.php | 1 + lang/ca/entities.php | 3 + lang/ca/settings.php | 4 + lang/cs/activities.php | 2 + lang/cs/auth.php | 1 + lang/cs/entities.php | 3 + lang/cs/settings.php | 4 + lang/cy/activities.php | 2 + lang/cy/auth.php | 1 + lang/cy/entities.php | 3 + lang/cy/settings.php | 4 + lang/da/activities.php | 2 + lang/da/auth.php | 1 + lang/da/entities.php | 3 + lang/da/settings.php | 4 + lang/de/activities.php | 2 + lang/de/auth.php | 3 +- lang/de/entities.php | 3 + lang/de/settings.php | 6 +- lang/de_informal/activities.php | 2 + lang/de_informal/auth.php | 1 + lang/de_informal/entities.php | 3 + lang/de_informal/settings.php | 4 + lang/el/activities.php | 2 + lang/el/auth.php | 1 + lang/el/entities.php | 3 + lang/el/settings.php | 4 + lang/es/activities.php | 2 + lang/es/auth.php | 1 + lang/es/entities.php | 3 + lang/es/settings.php | 4 + lang/es_AR/activities.php | 2 + lang/es_AR/auth.php | 1 + lang/es_AR/entities.php | 3 + lang/es_AR/settings.php | 4 + lang/et/activities.php | 2 + lang/et/auth.php | 1 + lang/et/entities.php | 5 +- lang/et/settings.php | 4 + lang/eu/activities.php | 2 + lang/eu/auth.php | 1 + lang/eu/entities.php | 3 + lang/eu/settings.php | 4 + lang/fa/activities.php | 2 + lang/fa/auth.php | 1 + lang/fa/entities.php | 3 + lang/fa/settings.php | 4 + lang/fi/activities.php | 2 + lang/fi/auth.php | 1 + lang/fi/entities.php | 3 + lang/fi/settings.php | 4 + lang/fr/activities.php | 2 + lang/fr/auth.php | 1 + lang/fr/entities.php | 3 + lang/fr/settings.php | 4 + lang/he/activities.php | 2 + lang/he/auth.php | 1 + lang/he/entities.php | 3 + lang/he/settings.php | 4 + lang/hr/activities.php | 2 + lang/hr/auth.php | 1 + lang/hr/entities.php | 3 + lang/hr/settings.php | 4 + lang/hu/activities.php | 36 +++-- lang/hu/auth.php | 63 ++++---- lang/hu/common.php | 12 +- lang/hu/components.php | 10 +- lang/hu/editor.php | 52 +++---- lang/hu/entities.php | 267 ++++++++++++++++---------------- lang/hu/errors.php | 110 ++++++------- lang/hu/notifications.php | 18 +-- lang/hu/passwords.php | 6 +- lang/hu/preferences.php | 34 ++-- lang/hu/settings.php | 232 +++++++++++++-------------- lang/hu/validation.php | 144 ++++++++--------- lang/id/activities.php | 2 + lang/id/auth.php | 1 + lang/id/entities.php | 3 + lang/id/settings.php | 4 + lang/is/activities.php | 2 + lang/is/auth.php | 1 + lang/is/entities.php | 3 + lang/is/settings.php | 4 + lang/it/activities.php | 2 + lang/it/auth.php | 1 + lang/it/entities.php | 3 + lang/it/settings.php | 6 +- lang/ja/activities.php | 2 + lang/ja/auth.php | 1 + lang/ja/entities.php | 3 + lang/ja/settings.php | 4 + lang/ka/activities.php | 2 + lang/ka/auth.php | 1 + lang/ka/entities.php | 3 + lang/ka/settings.php | 4 + lang/ko/activities.php | 2 + lang/ko/auth.php | 1 + lang/ko/entities.php | 3 + lang/ko/settings.php | 4 + lang/ku/activities.php | 2 + lang/ku/auth.php | 1 + lang/ku/entities.php | 3 + lang/ku/settings.php | 4 + lang/lt/activities.php | 2 + lang/lt/auth.php | 1 + lang/lt/entities.php | 3 + lang/lt/settings.php | 4 + lang/lv/activities.php | 2 + lang/lv/auth.php | 1 + lang/lv/entities.php | 3 + lang/lv/settings.php | 4 + lang/nb/activities.php | 2 + lang/nb/auth.php | 1 + lang/nb/entities.php | 3 + lang/nb/settings.php | 4 + lang/ne/activities.php | 2 + lang/ne/auth.php | 1 + lang/ne/entities.php | 3 + lang/ne/settings.php | 4 + lang/nl/activities.php | 2 + lang/nl/auth.php | 1 + lang/nl/entities.php | 3 + lang/nl/settings.php | 4 + lang/nn/activities.php | 2 + lang/nn/auth.php | 1 + lang/nn/entities.php | 3 + lang/nn/settings.php | 4 + lang/pl/activities.php | 2 + lang/pl/auth.php | 1 + lang/pl/entities.php | 3 + lang/pl/settings.php | 4 + lang/pt/activities.php | 2 + lang/pt/auth.php | 1 + lang/pt/entities.php | 3 + lang/pt/settings.php | 4 + lang/pt_BR/activities.php | 2 + lang/pt_BR/auth.php | 1 + lang/pt_BR/entities.php | 3 + lang/pt_BR/settings.php | 4 + lang/ro/activities.php | 2 + lang/ro/auth.php | 1 + lang/ro/entities.php | 3 + lang/ro/settings.php | 4 + lang/ru/activities.php | 2 + lang/ru/auth.php | 1 + lang/ru/entities.php | 3 + lang/ru/settings.php | 4 + lang/sk/activities.php | 2 + lang/sk/auth.php | 1 + lang/sk/entities.php | 3 + lang/sk/settings.php | 4 + lang/sl/activities.php | 2 + lang/sl/auth.php | 1 + lang/sl/entities.php | 3 + lang/sl/settings.php | 4 + lang/sq/activities.php | 2 + lang/sq/auth.php | 1 + lang/sq/entities.php | 3 + lang/sq/settings.php | 4 + lang/sr/activities.php | 2 + lang/sr/auth.php | 1 + lang/sr/entities.php | 3 + lang/sr/settings.php | 4 + lang/sv/activities.php | 2 + lang/sv/auth.php | 1 + lang/sv/entities.php | 3 + lang/sv/settings.php | 4 + lang/th/activities.php | 2 + lang/th/auth.php | 1 + lang/th/entities.php | 3 + lang/th/settings.php | 4 + lang/tk/activities.php | 2 + lang/tk/auth.php | 1 + lang/tk/entities.php | 3 + lang/tk/settings.php | 4 + lang/tr/activities.php | 2 + lang/tr/auth.php | 1 + lang/tr/entities.php | 3 + lang/tr/settings.php | 4 + lang/uk/activities.php | 2 + lang/uk/auth.php | 1 + lang/uk/entities.php | 3 + lang/uk/settings.php | 4 + lang/uz/activities.php | 2 + lang/uz/auth.php | 1 + lang/uz/entities.php | 3 + lang/uz/settings.php | 4 + lang/vi/activities.php | 2 + lang/vi/auth.php | 1 + lang/vi/entities.php | 3 + lang/vi/settings.php | 4 + lang/zh_CN/activities.php | 2 + lang/zh_CN/auth.php | 1 + lang/zh_CN/entities.php | 3 + lang/zh_CN/settings.php | 4 + lang/zh_TW/activities.php | 2 + lang/zh_TW/auth.php | 1 + lang/zh_TW/entities.php | 3 + lang/zh_TW/settings.php | 4 + 216 files changed, 1011 insertions(+), 491 deletions(-) diff --git a/lang/ar/activities.php b/lang/ar/activities.php index 69da1213846..efef9af92b1 100644 --- a/lang/ar/activities.php +++ b/lang/ar/activities.php @@ -99,6 +99,8 @@ '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-', diff --git a/lang/ar/auth.php b/lang/ar/auth.php index fb6f3d1cf72..6551df4dc3e 100644 --- a/lang/ar/auth.php +++ b/lang/ar/auth.php @@ -8,6 +8,7 @@ 'failed' => 'البيانات المعطاة لا توافق سجلاتنا.', 'throttle' => 'تجاوزت الحد الأقصى من المحاولات. الرجاء المحاولة مرة أخرى بعد :seconds ثانية/ثواني.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'إنشاء حساب', diff --git a/lang/ar/entities.php b/lang/ar/entities.php index 64192a021fd..59b0d52e065 100644 --- a/lang/ar/entities.php +++ b/lang/ar/entities.php @@ -331,6 +331,9 @@ // 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' => 'وسوم الكتاب', diff --git a/lang/ar/settings.php b/lang/ar/settings.php index a1fef8364d8..aa361e04b9a 100644 --- a/lang/ar/settings.php +++ b/lang/ar/settings.php @@ -264,6 +264,9 @@ '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', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/bg/activities.php b/lang/bg/activities.php index f5ed71a4cc6..344423e0d6c 100644 --- a/lang/bg/activities.php +++ b/lang/bg/activities.php @@ -99,6 +99,8 @@ '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', diff --git a/lang/bg/auth.php b/lang/bg/auth.php index 4a7e56f22e0..91c7495a108 100644 --- a/lang/bg/auth.php +++ b/lang/bg/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Въведените данни не съвпадат с информацията в системата.', 'throttle' => 'Твърде много опити за влизане. Опитайте пак след :seconds секунди.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Регистриране', diff --git a/lang/bg/entities.php b/lang/bg/entities.php index 19e4f518860..c501483a3c9 100644 --- a/lang/bg/entities.php +++ b/lang/bg/entities.php @@ -331,6 +331,9 @@ // 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' => 'Тагове на книгата', diff --git a/lang/bg/settings.php b/lang/bg/settings.php index 0554100fdd3..0af97414045 100644 --- a/lang/bg/settings.php +++ b/lang/bg/settings.php @@ -264,6 +264,9 @@ '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 маркер', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/bn/activities.php b/lang/bn/activities.php index c9268f8abe4..a9e04399d37 100644 --- a/lang/bn/activities.php +++ b/lang/bn/activities.php @@ -99,6 +99,8 @@ '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' => 'এপিআই টোকেনটি তৈরী করেছেন', diff --git a/lang/bn/auth.php b/lang/bn/auth.php index 30879fcb6d3..9e3c07b2cc8 100644 --- a/lang/bn/auth.php +++ b/lang/bn/auth.php @@ -8,6 +8,7 @@ 'failed' => 'প্রদত্ত তথ্যনিরূপিত কোন রেকর্ড পাওয়া যায়নি।', 'throttle' => 'লগইন প্রচেষ্টার সীমা অতিক্রান্ত। দয়া করে :seconds সেকেন্ড পর আবার চেষ্টা করুন।', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'নিবন্ধিত হোন', diff --git a/lang/bn/entities.php b/lang/bn/entities.php index 5501d2bc229..58c00ec4b27 100644 --- a/lang/bn/entities.php +++ b/lang/bn/entities.php @@ -331,6 +331,9 @@ // 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', diff --git a/lang/bn/settings.php b/lang/bn/settings.php index 1bc5d1551ae..ab7fe951271 100644 --- a/lang/bn/settings.php +++ b/lang/bn/settings.php @@ -264,6 +264,9 @@ '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', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/bs/activities.php b/lang/bs/activities.php index e7308770a29..bf07d4a5615 100644 --- a/lang/bs/activities.php +++ b/lang/bs/activities.php @@ -99,6 +99,8 @@ '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', diff --git a/lang/bs/auth.php b/lang/bs/auth.php index a8d0da78eb9..73848fbe178 100644 --- a/lang/bs/auth.php +++ b/lang/bs/auth.php @@ -8,6 +8,7 @@ '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', diff --git a/lang/bs/entities.php b/lang/bs/entities.php index b490c8c9405..879e2b3ea57 100644 --- a/lang/bs/entities.php +++ b/lang/bs/entities.php @@ -331,6 +331,9 @@ // 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', diff --git a/lang/bs/settings.php b/lang/bs/settings.php index 3937c650f86..d03024a89d6 100644 --- a/lang/bs/settings.php +++ b/lang/bs/settings.php @@ -264,6 +264,9 @@ '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', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/ca/activities.php b/lang/ca/activities.php index 4894c279aad..d797286cb8e 100644 --- a/lang/ca/activities.php +++ b/lang/ca/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'S’ha actualitzat l’usuari', 'user_delete' => 'ha suprimit l’usuari', 'user_delete_notification' => 'S’ha suprimit l’usuari', + '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', diff --git a/lang/ca/auth.php b/lang/ca/auth.php index ba6ede42f0c..c503c6b6bbf 100644 --- a/lang/ca/auth.php +++ b/lang/ca/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Aquestes credencials no existeixen al nostre registre.', 'throttle' => 'Massa intents d’inici de sessió. Torneu-ho a provar d’aquí :seconds segons.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registreu-vos', diff --git a/lang/ca/entities.php b/lang/ca/entities.php index edbfdbc4429..3a37d30376b 100644 --- a/lang/ca/entities.php +++ b/lang/ca/entities.php @@ -331,6 +331,9 @@ // 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', diff --git a/lang/ca/settings.php b/lang/ca/settings.php index 2a2106eb35e..ac60ce8e2cb 100644 --- a/lang/ca/settings.php +++ b/lang/ca/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Configureu l’autenticació multifactorial per a afegir una capa de seguretat extra al vostre compte d’usuari.', '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', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/cs/activities.php b/lang/cs/activities.php index f7e9337d3fa..3e497ecedbe 100644 --- a/lang/cs/activities.php +++ b/lang/cs/activities.php @@ -99,6 +99,8 @@ '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' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'API token byl vytvořen', diff --git a/lang/cs/auth.php b/lang/cs/auth.php index ad225a2fee2..4ad62ff1ae6 100644 --- a/lang/cs/auth.php +++ b/lang/cs/auth.php @@ -8,6 +8,7 @@ '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' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registrace', diff --git a/lang/cs/entities.php b/lang/cs/entities.php index 7db62f7bb49..e44dcf84e62 100644 --- a/lang/cs/entities.php +++ b/lang/cs/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Skrýt/Zobrazit postranní panel', + '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' => 'Štítky stránky', 'chapter_tags' => 'Štítky kapitoly', 'book_tags' => 'Štítky knihy', diff --git a/lang/cs/settings.php b/lang/cs/settings.php index a7ab9927d8a..c11b7ee586e 100644 --- a/lang/cs/settings.php +++ b/lang/cs/settings.php @@ -264,6 +264,9 @@ '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' => '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' => 'Vytvořit API Token', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/cy/activities.php b/lang/cy/activities.php index 2459f2357f5..da2ac372cf1 100644 --- a/lang/cy/activities.php +++ b/lang/cy/activities.php @@ -99,6 +99,8 @@ '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', diff --git a/lang/cy/auth.php b/lang/cy/auth.php index 5c4034fcdd4..44781a9bbe9 100644 --- a/lang/cy/auth.php +++ b/lang/cy/auth.php @@ -8,6 +8,7 @@ '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', diff --git a/lang/cy/entities.php b/lang/cy/entities.php index af1f6c43c92..0a042e7152a 100644 --- a/lang/cy/entities.php +++ b/lang/cy/entities.php @@ -331,6 +331,9 @@ // 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', diff --git a/lang/cy/settings.php b/lang/cy/settings.php index a0519cccf0f..816a4b89ffb 100644 --- a/lang/cy/settings.php +++ b/lang/cy/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Gosod dilysu aml-ffactor fel haen ychwanegol o ddiogelwch ar gyfer eich cyfrif defnyddiwr.', 'users_mfa_x_methods' => ':count dull wedi\'i ffurfweddu|:count dull wedi\'u ffurfweddu', 'users_mfa_configure' => 'Ffurfweddu Dulliau', + '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' => 'Creu Tocyn API', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/da/activities.php b/lang/da/activities.php index fa11e8e3afb..00cb76b5f78 100644 --- a/lang/da/activities.php +++ b/lang/da/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Brugeren blev opdateret', 'user_delete' => 'slettet bruger', 'user_delete_notification' => 'Brugeren blev fjernet', + 'user_mfa_reset' => 'nulstil MFA for brugeren', + 'user_mfa_reset_notification' => 'Nulstilling af metoder til multifaktor-godkendelse', // API Tokens 'api_token_create' => 'oprettet API token', diff --git a/lang/da/auth.php b/lang/da/auth.php index dcc531125d5..95ebd6230e6 100644 --- a/lang/da/auth.php +++ b/lang/da/auth.php @@ -8,6 +8,7 @@ 'failed' => 'De indtastede brugeroplysninger stemmer ikke overens med vores registreringer.', 'throttle' => 'For mange mislykkede loginforsøg. Prøv igen om :seconds sekunder.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registrer', diff --git a/lang/da/entities.php b/lang/da/entities.php index 0aa35cc5db4..f1c5735eddf 100644 --- a/lang/da/entities.php +++ b/lang/da/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Sidebjælke til/fra', + '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' => 'Sidetags', 'chapter_tags' => 'Kapiteltags', 'book_tags' => 'Bogtags', diff --git a/lang/da/settings.php b/lang/da/settings.php index fb5a1c958ab..448b00c4c07 100644 --- a/lang/da/settings.php +++ b/lang/da/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Opsæt multi-faktor godkendelse som et ekstra lag af sikkerhed for din brugerkonto.', 'users_mfa_x_methods' => ':count metode konfigureret|:count metoder konfigureret', 'users_mfa_configure' => 'Konfigurer metoder', + 'users_mfa_reset' => 'Nulstil metoder til multifaktor-godkendelse', + 'users_mfa_reset_desc' => 'Dette vil nulstille og slette alle konfigurerede metoder til multifaktor-godkendelse for denne bruger. Hvis multifaktor-godkendelse er påkrævet for en af brugerens roller, vil vedkommende blive bedt om at konfigurere nye metoder ved næste login.', + 'users_mfa_reset_confirm' => 'Er du sikker på, at du vil nulstille multifaktorautentificering for denne bruger?', // API Tokens 'user_api_token_create' => 'Opret API-token', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/de/activities.php b/lang/de/activities.php index 025cbaeffab..a680a72c514 100644 --- a/lang/de/activities.php +++ b/lang/de/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Benutzer erfolgreich aktualisiert', 'user_delete' => 'hat Benutzer gelöscht: ', 'user_delete_notification' => 'Benutzer erfolgreich entfernt', + 'user_mfa_reset' => 'Setze MFA für Nutzer zurück', + 'user_mfa_reset_notification' => 'Multifaktor-Authenifizierungsmethoden zurücksetzen', // API Tokens 'api_token_create' => 'API-Token erstellt', diff --git a/lang/de/auth.php b/lang/de/auth.php index 92789d18216..0ae2dcd9799 100644 --- a/lang/de/auth.php +++ b/lang/de/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Diese Anmeldedaten stimmen nicht mit unseren Aufzeichnungen überein.', 'throttle' => 'Zu viele Anmeldeversuche. Bitte versuchen Sie es in :seconds Sekunden erneut.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registrieren', @@ -39,7 +40,7 @@ 'register_success' => 'Vielen Dank für Ihre Registrierung! Die Daten sind gespeichert und Sie sind angemeldet.', // Login auto-initiation - 'auto_init_starting' => 'Anmeldeversuche', + 'auto_init_starting' => 'Anmeldeversuch', 'auto_init_starting_desc' => 'Wir verbinden uns mit Ihrem Authentifizierungssystem, um den Anmeldeprozess zu starten. Sollte es nach 5 Sekunden nicht weitergehen, klicken Sie bitte auf den unten stehenden Link.', 'auto_init_start_link' => 'Mit Authentifizierung fortfahren', diff --git a/lang/de/entities.php b/lang/de/entities.php index db5d070f444..a35a5f4c465 100644 --- a/lang/de/entities.php +++ b/lang/de/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Seitenleiste umschalten', + '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' => 'Seiten-Schlagwörter', 'chapter_tags' => 'Kapitel-Schlagwörter', 'book_tags' => 'Buch-Schlagwörter', diff --git a/lang/de/settings.php b/lang/de/settings.php index 691ee2ee68b..983de75baf6 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -147,7 +147,7 @@ 'recycle_bin_restore_confirm' => 'Durch diese Aktion wird das gelöschte Element einschließlich aller untergeordneten Elemente an seinem ursprünglichen Speicherort wiederhergestellt. Sollte der ursprüngliche Speicherort inzwischen gelöscht worden sein und sich nun im Papierkorb befinden, muss auch das übergeordnete Element wiederhergestellt werden.', 'recycle_bin_restore_deleted_parent' => 'Das übergeordnete Element dieses Eintrags wurde ebenfalls gelöscht. Diese Einträge bleiben gelöscht, bis auch das übergeordnete Element wiederhergestellt wird.', 'recycle_bin_restore_parent' => 'Übergeordneter Eintrag wiederherstellen', - 'recycle_bin_destroy_notification' => 'Löscht :count Elemente aus dem Papierkorb.', + 'recycle_bin_destroy_notification' => ':count Elemente aus dem Papierkorb gelöscht.', 'recycle_bin_restore_notification' => 'Es wurden :count der Elemente aus dem Papierkorb wiederhergestellt.', // Audit Log @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Richten Sie die Multi-Faktor-Authentifizierung als zusätzliche Sicherheitsstufe für Ihr Benutzerkonto ein.', 'users_mfa_x_methods' => ':count Methode konfiguriert|:count Methoden konfiguriert', 'users_mfa_configure' => 'Methoden konfigurieren', + 'users_mfa_reset' => 'Setze Multifaktor-Authentifizierung zurück', + 'users_mfa_reset_desc' => 'Dies wird alle konfigurierten Multifaktor-Authentifizierungsmethoden für diesen Nutzer zurücksetzen. Falls Multifaktor-Authentifizierung für eine seiner Rollen erforderlich ist, werden sie aufgefordert, neue Methoden beim nächsten Login zu konfigurieren.', + 'users_mfa_reset_confirm' => 'Sind Sie sicher, dass Sie diese Multi-Faktor-Authentifizierungsmethode für diesen Nutzer zurücksetzen möchten?', // API Tokens 'user_api_token_create' => 'Neuen API-Token erstellen', @@ -364,6 +367,7 @@ 'sk' => 'Slowenisch', 'sl' => 'Slowenisch', 'sv' => 'Schwedisch', + 'th' => 'ภาษาไทย', 'tr' => 'Türkisch', 'uk' => 'Ukrainisch', 'uz' => 'O‘zbekcha', diff --git a/lang/de_informal/activities.php b/lang/de_informal/activities.php index 990bfa0bc56..85a572c5cd9 100644 --- a/lang/de_informal/activities.php +++ b/lang/de_informal/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Benutzer erfolgreich aktualisiert', 'user_delete' => 'hat Benutzer gelöscht: ', 'user_delete_notification' => 'Benutzer erfolgreich entfernt', + 'user_mfa_reset' => 'Setze MFA für Nutzer zurück', + 'user_mfa_reset_notification' => 'Multifaktor-Authenifizierungsmethoden zurücksetzen', // API Tokens 'api_token_create' => 'API Token wurde erstellt', diff --git a/lang/de_informal/auth.php b/lang/de_informal/auth.php index 17dcdbcb26f..b91db255170 100644 --- a/lang/de_informal/auth.php +++ b/lang/de_informal/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Die eingegebenen Anmeldedaten sind ungültig.', 'throttle' => 'Zu viele Anmeldeversuche. Bitte versuche es in :seconds Sekunden erneut.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registrieren', diff --git a/lang/de_informal/entities.php b/lang/de_informal/entities.php index 708397e7e11..4b4c7e4ef5f 100644 --- a/lang/de_informal/entities.php +++ b/lang/de_informal/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Seitenleiste umschalten', + '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' => 'Seiten-Schlagwörter', 'chapter_tags' => 'Kapitel-Schlagwörter', 'book_tags' => 'Buch-Schlagwörter', diff --git a/lang/de_informal/settings.php b/lang/de_informal/settings.php index 93ed1902fb5..84e0c8b1042 100644 --- a/lang/de_informal/settings.php +++ b/lang/de_informal/settings.php @@ -265,6 +265,9 @@ 'users_mfa_desc' => 'Richte Multi-Faktor-Authentifizierung als zusätzliche Sicherheitsstufe für dein Benutzerkonto ein.', 'users_mfa_x_methods' => ':count Methode konfiguriert|:count Methoden konfiguriert', 'users_mfa_configure' => 'Methoden konfigurieren', + 'users_mfa_reset' => 'Setze Multifaktor-Authentifizierung zurück', + 'users_mfa_reset_desc' => 'Dies wird alle konfigurierten Multifaktor-Authentifizierungsmethoden für diesen Nutzer zurücksetzen. Falls Multifaktor-Authentifizierung für eine seiner Rollen erforderlich ist, werden sie aufgefordert, neue Methoden beim nächsten Login zu konfigurieren.', + 'users_mfa_reset_confirm' => 'Sind Sie sicher, dass Sie diese Multi-Faktor-Authentifizierungsmethode für diesen Nutzer zurücksetzen möchten?', // API Tokens 'user_api_token_create' => 'Neuen API-Token erstellen', @@ -365,6 +368,7 @@ 'sk' => 'Slowenisch', 'sl' => 'Slowenisch', 'sv' => 'Schwedisch', + 'th' => 'ภาษาไทย', 'tr' => 'Türkisch', 'uk' => 'Ukrainisch', 'uz' => 'O‘zbekcha', diff --git a/lang/el/activities.php b/lang/el/activities.php index 226ef254074..bd82628f47f 100644 --- a/lang/el/activities.php +++ b/lang/el/activities.php @@ -99,6 +99,8 @@ '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' => 'created API token', diff --git a/lang/el/auth.php b/lang/el/auth.php index 0b94ef859dd..b51208fbdf2 100644 --- a/lang/el/auth.php +++ b/lang/el/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Αυτά τα διαπιστευτήρια δεν ταιριάζουν με τα αρχεία μας.', 'throttle' => 'Πάρα πολλές προσπάθειες σύνδεσης. Δοκιμάστε ξανά σε :δευτερόλεπτα.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Εγγραφείτε', diff --git a/lang/el/entities.php b/lang/el/entities.php index b551d1a86cb..06b2633615f 100644 --- a/lang/el/entities.php +++ b/lang/el/entities.php @@ -331,6 +331,9 @@ // 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' => 'Ετικέτες Βιβλίου', diff --git a/lang/el/settings.php b/lang/el/settings.php index 605b8b40e01..42422a8e7da 100644 --- a/lang/el/settings.php +++ b/lang/el/settings.php @@ -264,6 +264,9 @@ '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 Token)', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/es/activities.php b/lang/es/activities.php index 89a11c63a2e..ecfffeb5194 100644 --- a/lang/es/activities.php +++ b/lang/es/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Usuario actualizado correctamente', 'user_delete' => 'usuario eliminado', 'user_delete_notification' => 'Usuario eliminado correctamente', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'token de API creado', diff --git a/lang/es/auth.php b/lang/es/auth.php index 45d3eb32916..467508001e9 100644 --- a/lang/es/auth.php +++ b/lang/es/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Estas credenciales no coinciden con nuestros registros.', 'throttle' => 'Demasiados intentos de inicio de sesión. Por favor, inténtalo de nuevo en :seconds segundos.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registrarse', diff --git a/lang/es/entities.php b/lang/es/entities.php index a8ec3b10ad2..7814161222b 100644 --- a/lang/es/entities.php +++ b/lang/es/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Mostrar/ocultar 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' => 'Etiquetas de Página', 'chapter_tags' => 'Etiquetas de Capítulo', 'book_tags' => 'Etiquetas de Libro', diff --git a/lang/es/settings.php b/lang/es/settings.php index 516480b6440..2fe672f83db 100644 --- a/lang/es/settings.php +++ b/lang/es/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'La autenticación en dos pasos añade una capa de seguridad adicional a tu cuenta.', 'users_mfa_x_methods' => ':count método configurado|:count métodos configurados', 'users_mfa_configure' => 'Configurar métodos', + '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' => 'Crear token API', @@ -364,6 +367,7 @@ 'sk' => 'Eslovaco', 'sl' => 'Esloveno', 'sv' => 'Sueco', + 'th' => 'ภาษาไทย', 'tr' => 'Turco', 'uk' => 'Ucraniano', 'uz' => 'O‘zbekcha', diff --git a/lang/es_AR/activities.php b/lang/es_AR/activities.php index e01fe4e643e..b4ba5690e0e 100644 --- a/lang/es_AR/activities.php +++ b/lang/es_AR/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Usuario actualizado con éxito', 'user_delete' => 'usuario eliminado', 'user_delete_notification' => 'El usuario fue eliminado correctamente', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'token de API creado', diff --git a/lang/es_AR/auth.php b/lang/es_AR/auth.php index 7687f709e92..ad69e651ea0 100644 --- a/lang/es_AR/auth.php +++ b/lang/es_AR/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Estas credenciales no concuerdan con nuestros registros.', 'throttle' => 'Demasiados intentos fallidos de inicio de sesión. Por favor intente nuevamente en :seconds segundos.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registrarse', diff --git a/lang/es_AR/entities.php b/lang/es_AR/entities.php index 2dc1bd64801..aac46b4477a 100644 --- a/lang/es_AR/entities.php +++ b/lang/es_AR/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Mostrar/ocultar 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' => 'Etiquetas de página', 'chapter_tags' => 'Etiquetas de capítulo', 'book_tags' => 'Etiquetas de libro', diff --git a/lang/es_AR/settings.php b/lang/es_AR/settings.php index 3b82d0fb35c..5545b91f3bc 100644 --- a/lang/es_AR/settings.php +++ b/lang/es_AR/settings.php @@ -265,6 +265,9 @@ 'users_mfa_desc' => 'Configure la autenticación de múltiples factores como una capa extra de seguridad para su cuenta de usuario.', 'users_mfa_x_methods' => ':count método configurado|:count métodos configurados', 'users_mfa_configure' => 'Configurar Métodos', + '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' => 'Crear token API', @@ -365,6 +368,7 @@ 'sk' => 'Eslovaco', 'sl' => 'Esloveno', 'sv' => 'Sueco', + 'th' => 'ภาษาไทย', 'tr' => 'Turco', 'uk' => 'Ucraniano', 'uz' => 'O‘zbekcha', diff --git a/lang/et/activities.php b/lang/et/activities.php index 3a86dcd8623..e42dd7fda36 100644 --- a/lang/et/activities.php +++ b/lang/et/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Kasutaja on muudetud', 'user_delete' => 'kustutas kasutaja', 'user_delete_notification' => 'Kasutaja on kustutatud', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'lisas API tunnuse', diff --git a/lang/et/auth.php b/lang/et/auth.php index 014a9f858eb..afbd2870bc7 100644 --- a/lang/et/auth.php +++ b/lang/et/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Kasutajanimi ja parool ei klapi.', 'throttle' => 'Liiga palju sisselogimiskatseid. Proovi uuesti :seconds sekundi pärast.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registreeru', diff --git a/lang/et/entities.php b/lang/et/entities.php index 4b7be605ac5..11c2f77aeb2 100644 --- a/lang/et/entities.php +++ b/lang/et/entities.php @@ -173,7 +173,7 @@ 'books_sort_desc' => 'Liiguta raamatu sees peatükke ja lehti, et selle sisu ümber organiseerida. Saad lisada teisi raamatuid, mis võimaldab peatükke ja lehti lihtsasti raamatute vahel liigutada. Lisaks saad määrata automaatse sorteerimise reegli, et selle raamatu sisu muudatuste puhul automaatselt järjestada.', 'books_sort_auto_sort' => 'Automaatne sorteerimine', 'books_sort_auto_sort_active' => 'Automaatne sorteerimine aktiivne: :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_auto_sort_creation_hint' => 'Automaatse sorteerimise reegleid saab lisada vajalike õigustega kasutaja "Loendid ja järjestamine" seadetes.', 'books_sort_named' => 'Sorteeri raamat :bookName', 'books_sort_name' => 'Sorteeri nime järgi', 'books_sort_created' => 'Sorteeri loomisaja järgi', @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Kuva/peida külgriba', + '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' => 'Lehe sildid', 'chapter_tags' => 'Peatüki sildid', 'book_tags' => 'Raamatu sildid', diff --git a/lang/et/settings.php b/lang/et/settings.php index fd00da9e3d2..3e28eae95f5 100644 --- a/lang/et/settings.php +++ b/lang/et/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Seadista mitmeastmeline autentimine, et oma kasutajakonto turvalisust tõsta.', 'users_mfa_x_methods' => ':count meetod seadistatud|:count meetodit seadistatud', 'users_mfa_configure' => 'Seadista meetodid', + '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' => 'Lisa API tunnus', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Sloveenia', 'sv' => 'Rootsi', + 'th' => 'ภาษาไทย', 'tr' => 'Türgi', 'uk' => 'Ukraina', 'uz' => 'O‘zbekcha', diff --git a/lang/eu/activities.php b/lang/eu/activities.php index 847014efe74..dadee1315d3 100644 --- a/lang/eu/activities.php +++ b/lang/eu/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Erabiltzailea egoki eguneratua', 'user_delete' => 'deleted user', 'user_delete_notification' => 'Erabiltzailea egoki ezabatua', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'created API token', diff --git a/lang/eu/auth.php b/lang/eu/auth.php index 35cbafd721b..6033035bd87 100644 --- a/lang/eu/auth.php +++ b/lang/eu/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Kredentzial hauek ez dira egokiak.', 'throttle' => 'Login saiakera kopurua pasa duzu. Mesedez, saiatu berriz :seconds segundu barru.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Izena eman', diff --git a/lang/eu/entities.php b/lang/eu/entities.php index 4d9e7cb064a..43277cc5500 100644 --- a/lang/eu/entities.php +++ b/lang/eu/entities.php @@ -331,6 +331,9 @@ // 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' => 'Orrialde etiketak', 'chapter_tags' => 'Kapitulu etiketak', 'book_tags' => 'Liburu etiketak', diff --git a/lang/eu/settings.php b/lang/eu/settings.php index dd346d1c9c9..ffa2c182e81 100644 --- a/lang/eu/settings.php +++ b/lang/eu/settings.php @@ -264,6 +264,9 @@ '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' => 'Sortu Tokena', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/fa/activities.php b/lang/fa/activities.php index 07eb78f3709..876c545a67f 100644 --- a/lang/fa/activities.php +++ b/lang/fa/activities.php @@ -99,6 +99,8 @@ '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', diff --git a/lang/fa/auth.php b/lang/fa/auth.php index c92f5f22b8a..1896663996c 100644 --- a/lang/fa/auth.php +++ b/lang/fa/auth.php @@ -8,6 +8,7 @@ 'failed' => 'مشخصات وارد شده با اطلاعات ما سازگار نیست.', 'throttle' => 'دفعات تلاش شما برای ورود بیش از حد مجاز است. لطفا پس از :seconds ثانیه مجددا تلاش فرمایید.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'ثبت نام', diff --git a/lang/fa/entities.php b/lang/fa/entities.php index 2abcff5768f..afeeca0e045 100644 --- a/lang/fa/entities.php +++ b/lang/fa/entities.php @@ -331,6 +331,9 @@ // 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' => 'برچسب های کتاب', diff --git a/lang/fa/settings.php b/lang/fa/settings.php index 21ad9624015..bb1b1ca7e2d 100644 --- a/lang/fa/settings.php +++ b/lang/fa/settings.php @@ -264,6 +264,9 @@ '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', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/fi/activities.php b/lang/fi/activities.php index a5ee14906ef..95c192e229f 100644 --- a/lang/fi/activities.php +++ b/lang/fi/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Käyttäjä päivitettiin onnistuneesti', 'user_delete' => 'poisti käyttäjän', 'user_delete_notification' => 'Käyttäjä poistettiin onnistuneesti', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'loi API-tunnisteen', diff --git a/lang/fi/auth.php b/lang/fi/auth.php index 580c8679726..16d6de346a4 100644 --- a/lang/fi/auth.php +++ b/lang/fi/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Annettuja käyttäjätietoja ei löydy.', 'throttle' => 'Liikaa kirjautumisyrityksiä. Yritä uudelleen :seconds sekunnin päästä.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Rekisteröidy', diff --git a/lang/fi/entities.php b/lang/fi/entities.php index dd2ad5e69d3..db53669bbae 100644 --- a/lang/fi/entities.php +++ b/lang/fi/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Näytä/piilota sivupalkki', + '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' => 'Sivun tunnisteet', 'chapter_tags' => 'Lukujen tunnisteet', 'book_tags' => 'Kirjojen tunnisteet', diff --git a/lang/fi/settings.php b/lang/fi/settings.php index 3aec0188670..aa8ac3e592b 100644 --- a/lang/fi/settings.php +++ b/lang/fi/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Paranna käyttäjätilisi turvallisuutta ja ota käyttöön monivaiheinen tunnistautuminen.', 'users_mfa_x_methods' => ':count menetelmä määritetty|:count menetelmää määritetty', 'users_mfa_configure' => 'Määritä menetelmiä', + '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' => 'Luo uusi API-tunniste', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/fr/activities.php b/lang/fr/activities.php index 0e70917da55..fcacc90898e 100644 --- a/lang/fr/activities.php +++ b/lang/fr/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Utilisateur mis à jour avec succès', 'user_delete' => 'utilisateur supprimé', 'user_delete_notification' => 'Utilisateur supprimé avec succès', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'a créé un jeton API', diff --git a/lang/fr/auth.php b/lang/fr/auth.php index 61ca0ee3e60..a7fe59d0ccb 100644 --- a/lang/fr/auth.php +++ b/lang/fr/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Ces informations ne correspondent à aucun compte.', 'throttle' => 'Trop d\'essais, veuillez réessayer dans :seconds secondes.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'S\'inscrire', diff --git a/lang/fr/entities.php b/lang/fr/entities.php index fae7480b11c..fa0808912a7 100644 --- a/lang/fr/entities.php +++ b/lang/fr/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Afficher/masquer la barre latérale', + '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' => 'Étiquettes de la page', 'chapter_tags' => 'Étiquettes du chapitre', 'book_tags' => 'Étiquettes du livre', diff --git a/lang/fr/settings.php b/lang/fr/settings.php index 664184a7d98..8ff81ba6bb3 100644 --- a/lang/fr/settings.php +++ b/lang/fr/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Configurer l\'authentification multi-facteurs ajoute une couche supplémentaire de sécurité à votre compte utilisateur.', 'users_mfa_x_methods' => ':count méthode configurée|:count méthodes configurées', 'users_mfa_configure' => 'Méthode de configuration', + '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' => 'Créer un nouveau jeton API', @@ -364,6 +367,7 @@ 'sk' => 'Slovaque', 'sl' => 'Slovène', 'sv' => 'Suédois', + 'th' => 'ภาษาไทย', 'tr' => 'Turc', 'uk' => 'Ukrainien', 'uz' => 'O‘zbekcha', diff --git a/lang/he/activities.php b/lang/he/activities.php index e94d53e0d37..52bf134d0c2 100644 --- a/lang/he/activities.php +++ b/lang/he/activities.php @@ -99,6 +99,8 @@ '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 Token נוצר', diff --git a/lang/he/auth.php b/lang/he/auth.php index 6f4f77223cf..6d3f04a61c2 100644 --- a/lang/he/auth.php +++ b/lang/he/auth.php @@ -8,6 +8,7 @@ 'failed' => 'פרטי ההתחברות אינם תואמים את הנתונים שלנו.', 'throttle' => 'נסיונות התחברות מהירים מדי, יש להמתין :seconds שניות ולנסות שנית.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'הרשמה למערכת', diff --git a/lang/he/entities.php b/lang/he/entities.php index 38ffc95cff0..12f92e1f046 100644 --- a/lang/he/entities.php +++ b/lang/he/entities.php @@ -331,6 +331,9 @@ // 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' => 'תגיות ספר', diff --git a/lang/he/settings.php b/lang/he/settings.php index b8f20481328..e816766813b 100644 --- a/lang/he/settings.php +++ b/lang/he/settings.php @@ -264,6 +264,9 @@ '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' => 'צור אסימון API', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/hr/activities.php b/lang/hr/activities.php index bf79f9ab14a..06fec08d29d 100644 --- a/lang/hr/activities.php +++ b/lang/hr/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Korisnik je uspješno ažuriran', 'user_delete' => 'izbrisani korisnik', 'user_delete_notification' => 'Korisnik je uspješno uklonjen', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'created API token', diff --git a/lang/hr/auth.php b/lang/hr/auth.php index bef07edcdaf..fcc81d5b075 100644 --- a/lang/hr/auth.php +++ b/lang/hr/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Ove vjerodajnice ne podudaraju se s našim zapisima.', 'throttle' => 'Previše pokušaja prijave. Molimo vas da pokušate za :seconds sekundi.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registrirajte se', diff --git a/lang/hr/entities.php b/lang/hr/entities.php index ff51c1b2f6f..320f85ecc50 100644 --- a/lang/hr/entities.php +++ b/lang/hr/entities.php @@ -331,6 +331,9 @@ // 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 knjiga', diff --git a/lang/hr/settings.php b/lang/hr/settings.php index 60b2485efb1..a595b5b1605 100644 --- a/lang/hr/settings.php +++ b/lang/hr/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Postavite višestruku provjeru autentičnosti kao dodatni sloj sigurnosti za svoj korisnički račun.', 'users_mfa_x_methods' => ':count metoda konfigurirano|:count metode konfigurirane', 'users_mfa_configure' => 'Konfiguriraj Metode', + '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' => 'Stvori API token', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/hu/activities.php b/lang/hu/activities.php index 68184e597b4..a64af5a187e 100644 --- a/lang/hu/activities.php +++ b/lang/hu/activities.php @@ -67,9 +67,9 @@ 'auth_password_reset_request' => 'jelszó visszaállítást kért', 'auth_password_reset_update' => 'felhasználói jelszó visszaállítás', 'mfa_setup_method' => 'MFA módszert állított be', - 'mfa_setup_method_notification' => 'Többfaktoros azonosítás sikeresen beállítva', + 'mfa_setup_method_notification' => 'Többlépcsős azonosítás sikeresen beállítva', 'mfa_remove_method' => 'MFA módszert törölt', - 'mfa_remove_method_notification' => 'Többfaktoros azonosítás sikeresen törölve', + 'mfa_remove_method_notification' => 'Többlépcsős azonosítás sikeresen törölve', // Settings 'settings_update' => 'frissítette a beállításokat', @@ -86,9 +86,9 @@ // Imports 'import_create' => 'import elkészült', - 'import_create_notification' => 'Az import sikeresen feltöltötve', + 'import_create_notification' => 'Az import sikeresen feltöltve', 'import_run' => 'import frissítve', - 'import_run_notification' => 'A tartalmat sikeresen importáltam.', + 'import_run_notification' => 'A tartalom sikeresen importálva', 'import_delete' => 'import törölve', 'import_delete_notification' => 'Az import sikeresen törölve', @@ -99,14 +99,16 @@ 'user_update_notification' => 'Felhasználó sikeresen frissítve', 'user_delete' => 'felhasználót törölt', 'user_delete_notification' => 'Felhasználó sikeresen eltávolítva', + 'user_mfa_reset' => 'MFA alaphelyzetbe állítva felhasználónak', + 'user_mfa_reset_notification' => 'Többlépcsős azonosítási módok alaphelyzetbe állítva', // API Tokens - 'api_token_create' => 'létrehozta az API tokent', - 'api_token_create_notification' => 'API token sikeresen létrehozva', - 'api_token_update' => 'frissítette az API tokent', - 'api_token_update_notification' => 'API token sikeresen frissítve', - 'api_token_delete' => 'törölte az API tokent', - 'api_token_delete_notification' => 'API token sikeresen törölve', + 'api_token_create' => 'létrehozta az API kulcsot', + 'api_token_create_notification' => 'API kulcs sikeresen létrehozva', + 'api_token_update' => 'frissítette az API kulcsot', + 'api_token_update_notification' => 'API kulcs sikeresen frissítve', + 'api_token_delete' => 'törölte az API kulcsot', + 'api_token_delete_notification' => 'API kulcs sikeresen törölve', // Roles 'role_create' => 'szerepkört hozott létre', @@ -118,14 +120,14 @@ // Recycle Bin 'recycle_bin_empty' => 'kiürítette a lomtárat', - 'recycle_bin_restore' => 'lomtárból visszaállítva', - 'recycle_bin_destroy' => 'lomtárból törölve', + 'recycle_bin_restore' => 'visszaállított a lomtárból', + 'recycle_bin_destroy' => 'törölte a lomtárból', // Comments - 'commented_on' => 'megjegyzést fűzött hozzá:', - 'comment_create' => 'hozzáadott hozzászólás', - 'comment_update' => 'frissített hozzászólás', - 'comment_delete' => 'megjegyzés törlése', + 'commented_on' => 'megjegyzést fűzött hozzá', + 'comment_create' => 'hozzáadott egy hozzászólást', + 'comment_update' => 'frissített egy hozzászólást', + 'comment_delete' => 'törölt egy hozzászólást', // Sort Rules 'sort_rule_create' => 'létrehozta a rendezési szabályt', @@ -136,5 +138,5 @@ 'sort_rule_delete_notification' => 'Rendezési szabály sikeresen törölve', // Other - 'permissions_update' => 'engedélyek frissítve', + 'permissions_update' => 'frissítette ez engedélyeket', ]; diff --git a/lang/hu/auth.php b/lang/hu/auth.php index 90669e2fba2..4e3fef6b092 100644 --- a/lang/hu/auth.php +++ b/lang/hu/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Ezek a hitelesítő adatok nem egyeznek a rögzítettekkel.', 'throttle' => 'Túl sok bejelentkezési próbálkozás. :seconds múlva lehet újra megpróbálni.', + 'mfa_throttle' => 'Túl sok többlépcsős azonosítási próbálkozás. :seconds múlva lehet újra megpróbálni.', // Login & Register 'sign_up' => 'Regisztráció', @@ -33,85 +34,85 @@ 'social_registration_text' => 'Regisztráció és bejelentkezés másik szolgáltatással.', 'register_thanks' => 'Köszönjük a regisztrációt!', - 'register_confirm' => 'Ellenőrizze a megadott e-mail címet, és kattintson a megerősítő gombra :appName eléréséhez.', + 'register_confirm' => 'Ellenőrizze a megadott email címet, és kattintson a megerősítő gombra :appName eléréséhez.', 'registrations_disabled' => 'A regisztráció jelenleg le van tiltva', 'registration_email_domain_invalid' => 'Ebből az email tartományról nem lehet hozzáférni ehhez az alkalmazáshoz', 'register_success' => 'Köszönjük a regisztrációt! A regisztráció és a bejelentkezés megtörtént.', // Login auto-initiation 'auto_init_starting' => 'Bejelentkezési kísérlet', - 'auto_init_starting_desc' => 'Kapcsolatba lépünk az azonosítási rendszereddel, hogy elkezdjük a bejelentkezési folyamatot. Ha 5 másodperc után sem történik előrelépés, próbálkozhatsz az alábbi linkre kattintva.', + 'auto_init_starting_desc' => 'Kapcsolatba lépünk az azonosítási rendszerrel, hogy elkezdjük a bejelentkezési folyamatot. Ha 5 másodperc után sem történik előrelépés, próbálkozhat az alábbi linkre kattintva.', 'auto_init_start_link' => 'Folytatás azonosítással', // Password Reset 'reset_password' => 'Jelszó visszaállítása', - 'reset_password_send_instructions' => 'Adja meg az e-mail címet, amire a jelszó-visszaállító linket küldjük.', + 'reset_password_send_instructions' => 'Adja meg az email címet, amire a jelszó-visszaállító linket küldjük.', 'reset_password_send_button' => 'Visszaállító hivatkozás elküldése', - 'reset_password_sent' => 'A jelszó-visszaállító linket e-mailben fogjuk elküldeni a(z) :email címre, ha beállításra került a rendszerben.', + 'reset_password_sent' => 'A jelszó-visszaállító linket emailben fogjuk elküldeni a(z) :email címre, ha található felhasználó ezzel a címmel a rendszerben.', 'reset_password_success' => 'A jelszó sikeresen visszaállítva.', 'email_reset_subject' => ':appName jelszó visszaállítása', - 'email_reset_text' => 'Ezt az e-mailt azért küldtük, mert egy jelszó-visszaállításra vonatkozó kérést kaptunk ebből a fiókból.', + 'email_reset_text' => 'Ezt az emailt azért küldtük, mert egy jelszó-visszaállításra vonatkozó kérést kaptunk ebből a fiókból.', 'email_reset_not_requested' => 'Ha nem Ön kérte a jelszó visszaállítását, akkor nincs szükség további intézkedésre.', // Email Confirmation 'email_confirm_subject' => ':appName alkalmazásban beállított email címet meg kell erősíteni', 'email_confirm_greeting' => ':appName köszöni a csatlakozást!', - 'email_confirm_text' => 'Az email címet a lenti gombra kattintva lehet megerősíteni:', + 'email_confirm_text' => 'Kérjük erősítse meg email címét a lenti gombra kattintva:', 'email_confirm_action' => 'Email megerősítése', - 'email_confirm_send_error' => 'Az e-mail megerősítés kötelező, de a rendszer nem tudta elküldeni az e-mailt. Keresse fel az adminisztrátort, és gondoskodjon róla, hogy az e-mail helyesen van beállítva.', - 'email_confirm_success' => 'Az Ön e-mail címe sikeresen meg lett erősítve, most már be tud jelentkezni az e-mail címe használatával.', - 'email_confirm_resent' => 'Megerősítő e-mail újraküldve. Ellenőrizze a bejövő üzeneteit!', + 'email_confirm_send_error' => 'Az email megerősítés kötelező, de a rendszer nem tudta elküldeni az emailt. Keresse fel az adminisztrátort, és gondoskodjon róla, hogy az email helyesen van beállítva.', + 'email_confirm_success' => 'Az Ön email címe sikeresen meg lett erősítve, most már be tud jelentkezni az email címe használatával.', + 'email_confirm_resent' => 'Megerősítő email újraküldve. Ellenőrizze a bejövő üzeneteit!', 'email_confirm_thanks' => 'Köszönjük a megerősítést!', - 'email_confirm_thanks_desc' => 'Kérjük, várjon egy pillanatot, amíg a megerősítést kezeljük. Ha nem kerül átirányításra 3 másodperc után, kattintson a lenti "Folytatás" linkre a továbbhaladáshoz.', + 'email_confirm_thanks_desc' => 'Kérjük, várjon egy pillanatot, amíg a megerősítést kezeljük. Ha nem kerül átirányításra 3 másodpercen belül, kattintson a lenti "Folytatás" linkre a továbbhaladáshoz.', 'email_not_confirmed' => 'Az email cím nincs megerősítve', - 'email_not_confirmed_text' => 'Az email cím még nincs megerősítve.', - 'email_not_confirmed_click_link' => 'Kattintson a regisztráció után nem sokkal elküldött e-mailben található hivatkozásra.', + 'email_not_confirmed_text' => 'Az Ön email címe még nincs megerősítve.', + 'email_not_confirmed_click_link' => 'Kattintson a regisztráció után nem sokkal elküldött emailben található hivatkozásra.', 'email_not_confirmed_resend' => 'Ha nem érkezik meg a megerősítő email, a lenti űrlap beküldésével újra lehet küldeni.', 'email_not_confirmed_resend_button' => 'Megerősítő email újraküldése', // User Invite - 'user_invite_email_subject' => 'Ez egy meghívó :appName weboldalhoz!', - 'user_invite_email_greeting' => 'Létre lett hozva egy fiók az :appName weboldalon.', - 'user_invite_email_text' => 'Jelszó beállításához és hozzáféréshez a lenti gombra kell kattintani:', + 'user_invite_email_subject' => 'Meghívták a(z) :appName weboldalra!', + 'user_invite_email_greeting' => 'Létre lett hozva egy fiók a(z) :appName weboldalon.', + 'user_invite_email_text' => 'Jelszó beállításához és hozzáféréshez kattintson a lenti gombra:', 'user_invite_email_action' => 'Fiók jelszó beállítása', 'user_invite_page_welcome' => ':appName üdvözöl!', - 'user_invite_page_text' => 'A fiók véglegesítéséhez és a hozzáféréshez be kell állítani egy jelszót ami :appName weboldalon lesz használva a bejelentkezéshez.', + 'user_invite_page_text' => 'A fiók véglegesítéséhez és a hozzáféréshez be kell állítania egy jelszót ami a(z) :appName weboldalon lesz használva a bejelentkezéshez.', 'user_invite_page_confirm_button' => 'Jelszó megerősítése', - 'user_invite_success_login' => 'Jelszó beállítva. Most már be tudsz jelentkezni a beállított jelszóval a következő rendszerbe: :appName!', + 'user_invite_success_login' => 'Jelszó beállítva. Most már be tud jelentkezni a beállított jelszóval a(z) :appName rendszerbe!', // Multi-factor Authentication 'mfa_setup' => 'Többlépcsős azonosítás beállítása', 'mfa_setup_desc' => 'Állítsa be a többlépcsős azonosítást egy extra biztonsági rétegként a felhasználói fiókjához.', 'mfa_setup_configured' => 'Már beállítva', 'mfa_setup_reconfigure' => 'Újrakonfigurálás', - 'mfa_setup_remove_confirmation' => 'Biztosan ki szeretné kapcsolni a többlépcsős azonosítást?', + 'mfa_setup_remove_confirmation' => 'Biztosan el szeretné távolítani ezt a többlépcsős azonosítási módot?', 'mfa_setup_action' => 'Beállítások', 'mfa_backup_codes_usage_limit_warning' => 'Kevesebb, mint 5 visszaállítási kódja maradt. Kérem, hogy generáljon új kódokat, hogy csökkentse a rendszerből való kizárásának esélyét.', 'mfa_option_totp_title' => 'Mobilalkalmazás', - 'mfa_option_totp_desc' => 'A többlépcsős azonosításhoz olyan mobilalkalmazásra lesz szükséged, amely támogatja a TOTP-t, például a Google Authenticator, az Authy vagy a Microsoft Authenticator.', - 'mfa_option_backup_codes_title' => 'Visszaállítási kulcsok', + 'mfa_option_totp_desc' => 'A többlépcsős azonosításhoz olyan mobilalkalmazásra lesz szüksége, amely támogatja a TOTP-t, például a Google Authenticator, az Authy vagy a Microsoft Authenticator.', + 'mfa_option_backup_codes_title' => 'Visszaállítási kódok', 'mfa_option_backup_codes_desc' => 'Egyszer használatos biztonsági kódokat hoz létre, amelyeket bejelentkezéskor kell megadnia személyazonosságának igazolására. Ügyeljen arra, hogy ezeket biztonságos helyen tárolja.', 'mfa_gen_confirm_and_enable' => 'Jóváhagyás és engedélyezés', 'mfa_gen_backup_codes_title' => 'Visszaállítási kódok beállítása', - 'mfa_gen_backup_codes_desc' => 'Tárolja el egy biztonságos helyen az alábbi kódokat. Bejelentkezés során fel tudja használni őket másodlagos bejelentkezési kódként.', + 'mfa_gen_backup_codes_desc' => 'Tárolja el egy biztonságos helyen az alábbi kódokat! Bejelentkezés során fel tudja használni őket másodlagos bejelentkezési kódként.', 'mfa_gen_backup_codes_download' => 'Kódok letöltése', - 'mfa_gen_backup_codes_usage_warning' => 'A kódok egyszerhasználatosak', + 'mfa_gen_backup_codes_usage_warning' => 'A kódok egyszer használatosak', 'mfa_gen_totp_title' => 'Mobilalkalmazás beállítása', - 'mfa_gen_totp_desc' => 'A többlépcsős azonosításhoz olyan mobilalkalmazásra lesz szükséged, amely támogatja a TOTP-t, például a Google Authenticator, az Authy vagy a Microsoft Authenticator.', - 'mfa_gen_totp_scan' => 'Szkenneld be az alábbi QR-kódot az általad használt azonosító alkalmazásoddal, hogy használhasd az alkalmazást.', + 'mfa_gen_totp_desc' => 'A többlépcsős azonosításhoz olyan mobilalkalmazásra lesz szüksége, amely támogatja a TOTP-t, például a Google Authenticator, az Authy vagy a Microsoft Authenticator.', + 'mfa_gen_totp_scan' => 'Olvassa be az alábbi QR-kódot az Ön által használt azonosító alkalmazással, hogy használhassa az alkalmazást.', 'mfa_gen_totp_verify_setup' => 'Beállítások ellenőrzése', - 'mfa_gen_totp_verify_setup_desc' => 'Ellenőrizd, hogy minden működik, azzal hogy beírod a kapott kódot amit az authentikátor alkalmazás generált az alábbi beviteli mezőbe:', - 'mfa_gen_totp_provide_code_here' => 'Add meg az alkalmazás által generált kódot ide', + 'mfa_gen_totp_verify_setup_desc' => 'Ellenőrizze, hogy minden működik azzal, hogy beírja az azonosító alkalmazás által generált kódot az alábbi mezőbe:', + 'mfa_gen_totp_provide_code_here' => 'Adja meg az alkalmazás által generált kódot ide', 'mfa_verify_access' => 'Hozzáférés ellenőrzése', - 'mfa_verify_access_desc' => 'Felhasználói fiókja megköveteli, hogy erősítse meg személyazonosságát egy további ellenőrzési szinttel, mielőtt hozzáférést kapna. A folytatáshoz használja az egyik konfigurált módszert.', - 'mfa_verify_no_methods' => 'Nincs konfigurálva MFA', - 'mfa_verify_no_methods_desc' => 'Nem található többlépcsős hitelesítési módszer a fiókjához. A hozzáféréshez legalább egy módszert be kell állítania.', + 'mfa_verify_access_desc' => 'Felhasználói fiókja megköveteli, hogy erősítse meg személyazonosságát egy további ellenőrzési szinttel, mielőtt hozzáférést kapna. A folytatáshoz használja az egyik beállított módot.', + 'mfa_verify_no_methods' => 'Nincsen beállítva mód', + 'mfa_verify_no_methods_desc' => 'Nem található többlépcsős hitelesítési mód a fiókjához. A hozzáféréshez legalább egy módot be kell állítania.', 'mfa_verify_use_totp' => 'Ellenőrzés mobil alkalmazás használatával', 'mfa_verify_use_backup_codes' => 'Ellenőrzés visszaállítási kóddal', 'mfa_verify_backup_code' => 'Visszaállítási kód', 'mfa_verify_backup_code_desc' => 'Adjon meg egy még fel nem használt visszaállítási kódot:', - 'mfa_verify_backup_code_enter_here' => 'Írd be a tartalék kódot', + 'mfa_verify_backup_code_enter_here' => 'Írja be a visszaállítási kódot', 'mfa_verify_totp_desc' => 'Írja be alább a mobilalkalmazásával generált kódot:', - 'mfa_setup_login_notification' => 'Többfaktoros hitelesítés konfigurálva. Kérjük, most jelentkezzen be újra a konfigurált módszerrel.', + 'mfa_setup_login_notification' => 'Többlépcsős hitelesítés beállítva. Kérjük, most jelentkezzen be újra a beállított módszerrel.', ]; diff --git a/lang/hu/common.php b/lang/hu/common.php index d25a765283d..080446892c2 100644 --- a/lang/hu/common.php +++ b/lang/hu/common.php @@ -20,7 +20,7 @@ 'description' => 'Leírás', 'role' => 'Szerepkör', 'cover_image' => 'Borítókép', - 'cover_image_description' => 'Ennek a képnek körülbelül 440 x 250 képpont méretűnek kell lennie, bár rugalmasan méretezhető és levágható, hogy a felhasználói felülethez illeszkedjen a különböző lehetőségek esetén, így a megjelenítés tényleges méretei eltérőek lesznek.', + 'cover_image_description' => 'Ennek a képnek körülbelül 440 x 250 pixel méretűnek kell lennie, bár rugalmasan méretezhető és levágható, hogy a felhasználói felülethez illeszkedjen a különböző alkalmazások esetén, így a megjelenítés tényleges méretei eltérőek lesznek.', // Actions 'actions' => 'Műveletek', @@ -53,7 +53,7 @@ 'filter_active' => 'Aktív szűrő:', 'filter_clear' => 'Szűrő törlése', 'download' => 'Letöltés', - 'open_in_tab' => 'Megnyitás új tab-on', + 'open_in_tab' => 'Megnyitás új fülön', 'open' => 'Megnyitás', // Sort Options @@ -70,7 +70,7 @@ 'deleted_user' => 'Törölt felhasználó', 'no_activity' => 'Nincs megjeleníthető aktivitás', 'no_items' => 'Nincsenek elérhető elemek', - 'back_to_top' => 'Oldal eleje', + 'back_to_top' => 'Oldal tetejére', 'skip_to_main_content' => 'Ugrás a fő tartalomra', 'toggle_details' => 'Részletek átkapcsolása', 'toggle_thumbnails' => 'Bélyegképek átkapcsolása', @@ -97,12 +97,12 @@ // Layout tabs 'tab_info' => 'Információ', - 'tab_info_label' => 'Tab: Másodlagos információk megjelenítése', + 'tab_info_label' => 'Fül: Másodlagos információk megjelenítése', 'tab_content' => 'Tartalom', - 'tab_content_label' => 'Tab: Elsődleges információk megjelenítése', + 'tab_content_label' => 'Fül: Elsődleges információk megjelenítése', // Email Content - 'email_action_help' => 'Probléma esetén a lenti ":actionText" gombra kell kattintani, majd ki kell másolni a lenti webcímet és be kell illeszteni egy böngészőbe:', + 'email_action_help' => 'Ha problémája van a(z) ":actionText" gombra kattintással, akkor másolja ki az URL-t, és illessze be a böngészőbe:', 'email_rights' => 'Minden jog fenntartva', // Footer Link Options diff --git a/lang/hu/components.php b/lang/hu/components.php index 6a54dd1b034..0b111df3844 100644 --- a/lang/hu/components.php +++ b/lang/hu/components.php @@ -9,8 +9,8 @@ 'image_list' => 'Képek listája', 'image_details' => 'A kép részletei', 'image_upload' => 'Kép feltöltése', - 'image_intro' => 'Itt választhatsz ki és kezelhetsz olyan képeket, amelyeket korábban feltöltöttek a rendszerbe.', - 'image_intro_upload' => 'Húzz ide egy új képfájlt az új kép feltöltéséhez, vagy használd a fenti "Kép feltöltése" gombot.', + 'image_intro' => 'Itt kiválaszthatja és kezelheti a rendszerbe korábban feltöltött képeket.', + 'image_intro_upload' => 'Húzzon ide egy új képfájlt az új kép feltöltéséhez, vagy használja a fenti "Kép feltöltése" gombot.', 'image_all' => 'Összes', 'image_all_title' => 'Összes kép megtekintése', 'image_book_title' => 'A könyvhöz feltöltött képek megtekintése', @@ -22,8 +22,8 @@ 'image_updated' => 'Frissítve ekkor: :updateDate', 'image_load_more' => 'Több betöltése', 'image_image_name' => 'Kép neve', - 'image_delete_used' => 'Ez a kép a lenti oldalakon van használatban.', - 'image_delete_confirm_text' => 'Biztosan törölhető ez a kép?', + 'image_delete_used' => 'Ez a kép az alábbi oldalakon van használatban.', + 'image_delete_confirm_text' => 'Biztosan törli ezt a képet?', 'image_select_image' => 'Kép kiválasztása', 'image_dropzone' => 'Képek feltöltése ejtéssel vagy kattintással', 'image_dropzone_drop' => 'Húzza a képeket ide a feltöltéshez', @@ -34,7 +34,7 @@ 'image_delete_success' => 'Kép sikeresen törölve', 'image_replace' => 'Kép cseréje', 'image_replace_success' => 'Képfájl sikeresen frissítve', - 'image_rebuild_thumbs' => 'Méret variációk újragenerálása', + 'image_rebuild_thumbs' => 'Méret változatok újragenerálása', 'image_rebuild_thumbs_success' => 'Kép méret változatok sikeresen újra lettek generálva!', // Code Editor diff --git a/lang/hu/editor.php b/lang/hu/editor.php index 705bc6d54c0..885e6951f8a 100644 --- a/lang/hu/editor.php +++ b/lang/hu/editor.php @@ -13,7 +13,7 @@ 'cancel' => 'Mégsem', 'save' => 'Mentés', 'close' => 'Bezárás', - 'apply' => 'Apply', + 'apply' => 'Alkalmaz', 'undo' => 'Visszavonás', 'redo' => 'Újra', 'left' => 'Balra', @@ -48,7 +48,7 @@ 'superscript' => 'Felső index', 'subscript' => 'Alsó index', 'text_color' => 'Szöveg szín', - 'highlight_color' => 'Highlight color', + 'highlight_color' => 'Kiemelő szín', 'custom_color' => 'Egyéni szín', 'remove_color' => 'Szín eltávolítása', 'background_color' => 'Háttérszín', @@ -65,14 +65,14 @@ 'insert_image' => 'Kép beszúrása', 'insert_image_title' => 'Kép beszúrása/szerkesztése', 'insert_link' => 'Hivatkozás beszúrása/szerkesztése', - 'insert_link_title' => 'Hivatkozás Beszúrása/Szerkesztése', + 'insert_link_title' => 'Hivatkozás beszúrása/szerkesztése', 'insert_horizontal_line' => 'Vízszintes vonal beszúrása', 'insert_code_block' => 'Kódrészlet beszúrása', - 'edit_code_block' => 'Kódrészlet beszúrása', + 'edit_code_block' => 'Kódrészlet szerkesztése', 'insert_drawing' => 'Rajz beszúrása/szerkesztése', 'drawing_manager' => 'Rajzkezelő', - 'insert_media' => 'Media beszúrása/szerkesztése', - 'insert_media_title' => 'Media Beszúrása/Szerkesztése', + 'insert_media' => 'Média beszúrása/szerkesztése', + 'insert_media_title' => 'Média beszúrása/szerkesztése', 'clear_formatting' => 'Formázás törlése', 'source_code' => 'Forráskód', 'source_code_title' => 'Forráskód', @@ -81,11 +81,11 @@ // Tables 'table_properties' => 'Táblázat tulajdonságai', - 'table_properties_title' => 'Táblázat Tulajdonságai', + 'table_properties_title' => 'Táblázat tulajdonságai', 'delete_table' => 'Táblázat törlése', - 'table_clear_formatting' => 'Tábla formázás törlése', + 'table_clear_formatting' => 'Táblázat formázás törlése', 'resize_to_contents' => 'Átméretezés a tartalomhoz', - 'row_header' => 'Sorfejléc', + 'row_header' => 'Fejléc sor', 'insert_row_before' => 'Sor beszúrása elé', 'insert_row_after' => 'Sor beszúrása mögé', 'delete_row' => 'Sor törlése', @@ -96,7 +96,7 @@ 'table_row' => 'Sor', 'table_column' => 'Oszlop', 'cell_properties' => 'Cella tulajdonságai', - 'cell_properties_title' => 'Cella Tulajdonságai', + 'cell_properties_title' => 'Cella tulajdonságai', 'cell_type' => 'Cella típusa', 'cell_type_cell' => 'Cella', 'cell_scope' => 'Hatáskör', @@ -111,7 +111,7 @@ 'border_style' => 'Szegély stílusa', 'border_color' => 'Szegély színe', 'row_properties' => 'Sor tulajdonságai', - 'row_properties_title' => 'Sor Tulajdonságai', + 'row_properties_title' => 'Sor tulajdonságai', 'cut_row' => 'Sor kivágása', 'copy_row' => 'Sor másolása', 'paste_row_before' => 'Sor beillesztése elé', @@ -125,10 +125,10 @@ 'copy_column' => 'Oszlop másolása', 'paste_column_before' => 'Oszlop beszúrása elé', 'paste_column_after' => 'Oszlop beszúrása utána', - 'cell_padding' => 'Cellatávolság', + 'cell_padding' => 'Cellamargó', 'cell_spacing' => 'Cellatávolság', 'caption' => 'Felirat', - 'show_caption' => 'Képaláírás mutatása', + 'show_caption' => 'Felirat megjelenítése', 'constrain' => 'Arányok megőrzése', 'cell_border_solid' => 'Folyamatos', 'cell_border_dotted' => 'Pontozott', @@ -145,33 +145,33 @@ 'source' => 'Forrás', 'alt_desc' => 'Alternatív leírás', 'embed' => 'Beágyazás', - 'paste_embed' => 'Illeszd be a beágyazási kódot ide:', + 'paste_embed' => 'Illessze be a beágyazási kódot ide:', 'url' => 'URL', - 'text_to_display' => 'Megjelenő szöveg', + 'text_to_display' => 'Megjelenítendő szöveg', 'title' => 'Cím', - 'browse_links' => 'Browse links', + 'browse_links' => 'Hivatkozások tallózása', 'open_link' => 'Hivatkozás megnyitása', 'open_link_in' => 'Hivatkozás megnyitása...', 'open_link_current' => 'Aktuális ablak', 'open_link_new' => 'Új ablak', 'remove_link' => 'Hivatkozás eltávolítása', - 'insert_collapsible' => 'Illeszd be az összecsukható blokkot', - 'collapsible_unwrap' => 'Kicsomagol', + 'insert_collapsible' => 'Összecsukható blokk beszúrása', + 'collapsible_unwrap' => 'Eltávolítás', 'edit_label' => 'Címke szerkesztése', - 'toggle_open_closed' => 'Nyitott/zárt váltása', + 'toggle_open_closed' => 'Nyitott/zárt állapot váltása', 'collapsible_edit' => 'Összecsukható blokk szerkesztése', - 'toggle_label' => 'Címke ki-be kapcsolása', + 'toggle_label' => 'Címke ki/be kapcsolása', // About view - 'about' => 'A szerkesztőről', + 'about' => 'A szerkesztő névjegye', 'about_title' => 'A WYSIWYG szerkesztőről', - 'editor_license' => 'Szerkesztő Licensz és Copyright információi', - 'editor_lexical_license' => 'This editor is built as a fork of :lexicalLink which is distributed under the MIT license.', - 'editor_lexical_license_link' => 'Full license details can be found here.', + 'editor_license' => 'Szerkesztő licenc és jogi információi', + 'editor_lexical_license' => 'Ez a szerkesztő az MIT licenc alatt szolgáltatott :lexicalLink segítségével készült.', + 'editor_lexical_license_link' => 'A teljes licenc az itt található.', 'editor_tiny_license' => 'Ez a szerkesztő az MIT licenc alatt szolgáltatott :tinyLink segítségével készült.', 'editor_tiny_license_link' => 'A TinyMCE szerzői jogi és licencinformációi itt találhatók.', - 'save_continue' => 'Mentés és Folytatás', - 'callouts_cycle' => '(Folyamatos lenyomva tartással válassza ki a típusok közötti váltást)', + 'save_continue' => 'Oldal mentése és folytatás', + 'callouts_cycle' => '(Ismételt lenyomással váltogathat a típusok között)', 'link_selector' => 'Tartalom hivatkozása', 'shortcuts' => 'Gyorsbillentyűk', 'shortcut' => 'Gyorsbillentyű', diff --git a/lang/hu/entities.php b/lang/hu/entities.php index 707de62d871..b499a9b6b72 100644 --- a/lang/hu/entities.php +++ b/lang/hu/entities.php @@ -44,45 +44,45 @@ 'default_template_explain' => 'Rendeljen hozzá egy oldalsablont, amely alapértelmezett tartalomként lesz használva az ezen az elemen belül létrehozott összes oldalon. Ne feledje, hogy ezt csak akkor használja, ha az oldal készítője megtekintési hozzáféréssel rendelkezik a kiválasztott sablonoldalhoz.', 'default_template_select' => 'Válasszon ki egy oldalsablont', 'import' => 'Import', - 'import_validate' => 'Validate Import', - 'import_desc' => 'Import books, chapters & pages using a portable zip export from the same, or a different, instance. Select a ZIP file to proceed. After the file has been uploaded and validated you\'ll be able to configure & confirm the import in the next view.', - 'import_zip_select' => 'Select ZIP file to upload', - 'import_zip_validation_errors' => 'Errors were detected while validating the provided ZIP file:', - 'import_pending' => 'Pending Imports', - 'import_pending_none' => 'No imports have been started.', - 'import_continue' => 'Continue Import', - 'import_continue_desc' => 'Review the content due to be imported from the uploaded ZIP file. When ready, run the import to add its contents to this system. The uploaded ZIP import file will be automatically removed on successful import.', - 'import_details' => 'Import Details', - 'import_run' => 'Run Import', - 'import_size' => ':size Import ZIP Size', - 'import_uploaded_at' => 'Uploaded :relativeTime', - 'import_uploaded_by' => 'Uploaded by', - 'import_location' => 'Import Location', - 'import_location_desc' => 'Select a target location for your imported content. You\'ll need the relevant permissions to create within the location you choose.', - 'import_delete_confirm' => 'Are you sure you want to delete this import?', - 'import_delete_desc' => 'This will delete the uploaded import ZIP file, and cannot be undone.', - 'import_errors' => 'Import Errors', - 'import_errors_desc' => 'The follow errors occurred during the import attempt:', - 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', - 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter', - 'breadcrumb_siblings_for_book' => 'Navigate siblings for book', - 'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf', + 'import_validate' => 'Importált adatok ellenőrzése', + 'import_desc' => 'Importáljon könyveket, fejezeteket és oldalakat egy azonos, vagy egy másik alkalmazásból származó ZIP állományból. A fájl feltöltése és ellenőrzése után beállíthatja és elindíthatja az importálást a következő oldalon.', + 'import_zip_select' => 'Feltöltendő ZIP fájl kiválasztása', + 'import_zip_validation_errors' => 'A megadott ZIP fájlban hibák találhatók:', + 'import_pending' => 'Függő importálások', + 'import_pending_none' => 'Nincs elindított importálás.', + 'import_continue' => 'Importálás folytatása', + 'import_continue_desc' => 'Importálás előtt nézze át a ZIP fájlból betöltendő tartalmakat. Amikor készen áll, futtassa le az importálást, hogy hozzáadja az állományokat a rendszerhez. A feltöltött ZIP fájl az importálás után automatikusan törlésre kerül.', + 'import_details' => 'Importálás részletei', + 'import_run' => 'Importálás', + 'import_size' => ':size méretű ZIP', + 'import_uploaded_at' => 'Feltöltve:', + 'import_uploaded_by' => 'Feltöltötte:', + 'import_location' => 'Importálás helye', + 'import_location_desc' => 'Válasszon ki egy helyet az importált tartalomnak. Létrehozási jogosultsággal kell rendelkezni a kiválasztott helyen.', + 'import_delete_confirm' => 'Biztosan törölni akarja ezt az importálást?', + 'import_delete_desc' => 'Ez véglegesen el fogja távolítani a feltöltött ZIP fájlt.', + 'import_errors' => 'Importálási hibák', + 'import_errors_desc' => 'A következő hibák jelentkeztek az importálás során:', + 'breadcrumb_siblings_for_page' => 'Navigáció az oldal szomszédaihoz', + 'breadcrumb_siblings_for_chapter' => 'Navigáció a fejezet szomszédaihoz', + 'breadcrumb_siblings_for_book' => 'Navigáció a könyv szomszédaihoz', + 'breadcrumb_siblings_for_bookshelf' => 'Navigáció a polc szomszédaihoz', // Permissions and restrictions 'permissions' => 'Jogosultságok', 'permissions_desc' => 'Itt állítsa be az engedélyeket a felhasználói szerepkörök által biztosított alapértelmezett engedélyek felülbírálásához.', - 'permissions_book_cascade' => 'A könyvekre beállított engedélyek automatikusan az alárendelt fejezetekhez és oldalakhoz kapcsolódnak, kivéve, ha saját engedélyekkel rendelkeznek.', - 'permissions_chapter_cascade' => 'A fejezetekre beállított engedélyek automatikusan az alárendelt oldalakra lépnek át, hacsak nem rendelkeznek saját engedélyekkel.', + 'permissions_book_cascade' => 'A könyvekre beállított engedélyek automatikusan az alárendelt fejezetekre és oldalakra is érvényesek, kivéve, ha azok saját engedély beállításokkal rendelkeznek.', + 'permissions_chapter_cascade' => 'A fejezetekre beállított engedélyek automatikusan az alárendelt oldalakra is érvényesek, kivéve, ha azok saját engedély beállításokkal rendelkeznek.', 'permissions_save' => 'Jogosultságok mentése', 'permissions_owner' => 'Tulajdonos', 'permissions_role_everyone_else' => 'Mindenki más', - 'permissions_role_everyone_else_desc' => 'Állítson be engedélyeket az összes, kifejezetten nem felülírt szerepkörhöz.', + 'permissions_role_everyone_else_desc' => 'Állítson be engedélyeket az összes nem felülírt szerepkörhöz.', 'permissions_role_override' => 'A szerepkör engedélyeinek felülbírálása', - 'permissions_inherit_defaults' => 'Alapértelmezett értékek öröklése', + 'permissions_inherit_defaults' => 'Alapértelmezett engedélyek öröklése', // Search 'search_results' => 'Keresési eredmények', - 'search_total_results_found' => ':count találat|összesen :count találat', + 'search_total_results_found' => ':count találat|Összesen :count találat', 'search_clear' => 'Keresés törlése', 'search_no_pages' => 'Nincsenek a keresésnek megfelelő oldalak', 'search_for_term' => ':term keresése', @@ -110,7 +110,7 @@ // Shelves 'shelf' => 'Polc', 'shelves' => 'Polcok', - 'x_shelves' => ':count polc|:count polcok', + 'x_shelves' => ':count polc|:count polc', 'shelves_empty' => 'Nincsenek könyvespolcok létrehozva', 'shelves_create' => 'Új polc létrehozása', 'shelves_popular' => 'Népszerű polcok', @@ -121,24 +121,24 @@ 'shelves_save' => 'Polc mentése', 'shelves_books' => 'Könyvek ezen a polcon', 'shelves_add_books' => 'Könyvek hozzáadása ehhez a polchoz', - 'shelves_drag_books' => 'Könyveket áthúzással lehet elhelyezni ezen a polcon', + 'shelves_drag_books' => 'Húzzon ide könyveket a polchoz hozzáadáshoz', 'shelves_empty_contents' => 'Ehhez a polchoz nincsenek könyvek rendelve', 'shelves_edit_and_assign' => 'Polc szerkesztése könyvek hozzárendeléséhez', 'shelves_edit_named' => ':name polc szerkesztése', 'shelves_edit' => 'Polc szerkesztése', 'shelves_delete' => 'Polc törlése', 'shelves_delete_named' => ':name polc törlése', - 'shelves_delete_explain' => "':name'. nevű polc ezzel le lesz törölve. A benne található könyvek nem lesznek törölve.", - 'shelves_delete_confirmation' => 'Biztosan törölhető ez a polc?', - 'shelves_permissions' => 'Polc jogosultság', + 'shelves_delete_explain' => "Ez törölni fogja a(z) ':name' nevű polcot. A benne található könyvek nem lesznek törölve.", + 'shelves_delete_confirmation' => 'Biztosan törli ezt a polcot?', + 'shelves_permissions' => 'Polc jogosultságok', 'shelves_permissions_updated' => 'Polc jogosultságok frissítve', 'shelves_permissions_active' => 'Polc jogosultságok aktívak', - 'shelves_permissions_cascade_warning' => 'A polcokhoz kapcsolódó jogosultságok nem kapcsolódnak automatikusan a tárolt könyvekhez. Ennek az az oka, hogy egy könyv több polcon is létezhet. Az engedélyek azonban lemásolhatók a gyermekkönyvekbe az alábbi lehetőség segítségével.', - 'shelves_permissions_create' => 'A polclétrehozási jogosultságok csak az alárendelt könyvekbe való másoláshoz használhatók az alábbi művelettel. Nem szabályozzák a könyvek létrehozásának lehetőségét.', - 'shelves_copy_permissions_to_books' => 'Jogosultság másolása könyvekre', - 'shelves_copy_permissions' => 'Jogosultság másolása', - 'shelves_copy_permissions_explain' => 'Ezzel a polc jelenlegi engedélybeállításait alkalmazza a benne található összes könyvre. Az aktiválás előtt győződjön meg arról, hogy a polc engedélyeinek módosításait elmentette.', - 'shelves_copy_permission_success' => 'Könyvespolc jogosultságok átmásolva :count könyvre', + 'shelves_permissions_cascade_warning' => 'A polcokhoz kapcsolódó jogosultságok nem vonatkoznak automatikusan a hozzájuk rendelt könyvekre. Ennek az az oka, hogy egy könyv több polchoz is tartozhat. Az engedélyek azonban lemásolhatók a könyvekre az alábbi opcióval.', + 'shelves_permissions_create' => 'A polc létrehozási jogosultságok csak a jogosultságok a hozzárendelt könyvekre másolásához vannak használva. Nem szabályozzák a könyvek létrehozásának lehetőségét.', + 'shelves_copy_permissions_to_books' => 'Jogosultságok másolása könyvekre', + 'shelves_copy_permissions' => 'Jogosultságok másolása', + 'shelves_copy_permissions_explain' => 'Ezzel alkalmazza a polc jelenlegi engedély beállításait a benne található összes könyvre. Az aktiválás előtt győződjön meg arról, hogy a polc engedélyeinek módosításait elmentette.', + 'shelves_copy_permission_success' => 'Polc jogosultságok átmásolva :count könyvre', // Books 'book' => 'Könyv', @@ -154,8 +154,8 @@ 'books_create' => 'Új könyv létrehozása', 'books_delete' => 'Könyv törlése', 'books_delete_named' => ':bookName könyv törlése', - 'books_delete_explain' => '\':bookName\' nevű könyv törölve lesz. Minden oldal és fejezet el lesz távolítva.', - 'books_delete_confirmation' => 'Biztosan törölhető ez a könyv?', + 'books_delete_explain' => 'Ez törölni fogja a(z) \':bookName\' nevű könyvet. Minden oldal és fejezet el lesz távolítva.', + 'books_delete_confirmation' => 'Biztosan törli ezt a könyvet?', 'books_edit' => 'Könyv szerkesztése', 'books_edit_named' => ':bookName könyv szerkesztése', 'books_form_book_name' => 'Könyv neve', @@ -170,10 +170,10 @@ 'books_search_this' => 'Keresés ebben a könyvben', 'books_navigation' => 'Könyv navigáció', 'books_sort' => 'Könyv tartalmak rendezése', - '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_desc' => 'Rendezze át egy könyv tartalmát a fejezetek és oldalak mozgatásával. Más könyvek is hozzáadhatók, így könnyű az átmozgatás könyvek között is. Opcionálisan egy rendezési szabály is megadható, hogy minden változtatáskor automatikusan rendezze a könyv tartalmát.', + 'books_sort_auto_sort' => 'Automatikus rendezés opció', + 'books_sort_auto_sort_active' => 'Aktív automatikus rendezés: :sortName', + 'books_sort_auto_sort_creation_hint' => 'Automatikus rendezési szabályok a "Listák és rendezés" beállításoknál hozhatók létre egy megfelelő jogosultságokkal rendelkező felhasználó által.', 'books_sort_named' => ':bookName könyv rendezése', 'books_sort_name' => 'Rendezés név szerint', 'books_sort_created' => 'Rendezés létrehozás dátuma szerint', @@ -181,7 +181,7 @@ 'books_sort_chapters_first' => 'Fejezetek elől', 'books_sort_chapters_last' => 'Fejezetek hátul', 'books_sort_show_other' => 'Egyéb könyvek mutatása', - 'books_sort_save' => 'Új elrendezés mentése', + 'books_sort_save' => 'Új sorrend mentése', 'books_sort_show_other_desc' => 'Adjon hozzá más könyveket, hogy bevonja őket a rendezési műveletbe, és lehetővé tegye a könyvek közötti egyszerű átszervezést.', 'books_sort_move_up' => 'Mozgatás fel', 'books_sort_move_down' => 'Mozgatás le', @@ -191,7 +191,7 @@ 'books_sort_move_next_chapter' => 'Mozgatás a következő fejezetbe', 'books_sort_move_book_start' => 'Mozgatás a könyv elejére', 'books_sort_move_book_end' => 'Mozgatás a könyv végére', - 'books_sort_move_before_chapter' => 'Morgazás a fejezet elé', + 'books_sort_move_before_chapter' => 'Mozgatás a fejezet elé', 'books_sort_move_after_chapter' => 'Mozgatás a fejezet után', 'books_copy' => 'Könyv másolása', 'books_copy_success' => 'Könyv sikeresen lemásolva', @@ -199,14 +199,14 @@ // Chapters 'chapter' => 'Fejezet', 'chapters' => 'Fejezetek', - 'x_chapters' => ':count fejezet|:count fejezetek', + 'x_chapters' => ':count fejezet|:count fejezet', 'chapters_popular' => 'Népszerű fejezetek', 'chapters_new' => 'Új fejezet', 'chapters_create' => 'Új fejezet létrehozása', 'chapters_delete' => 'Fejezet törlése', 'chapters_delete_named' => ':chapterName fejezet törlése', - 'chapters_delete_explain' => 'A(z) \':chapterName\' törlésére készül. A fejezethez tartozó minden oldal is törlésre fog kerülni.', - 'chapters_delete_confirm' => 'Biztosan törölhető ez a fejezet?', + 'chapters_delete_explain' => 'Ez törölni fogja a(z) \':chapterName\' fejezetet. A fejezethez tartozó minden oldal is törlésre fog kerülni.', + 'chapters_delete_confirm' => 'Biztosan törli ezt a fejezetet?', 'chapters_edit' => 'Fejezet szerkesztése', 'chapters_edit_named' => ':chapterName fejezet szerkesztése', 'chapters_save' => 'Fejezet mentése', @@ -235,32 +235,32 @@ 'pages_delete_draft' => 'Vázlat oldal törlése', 'pages_delete_success' => 'Oldal törölve', 'pages_delete_draft_success' => 'Vázlat oldal törölve', - 'pages_delete_warning_template' => 'Ez az oldal aktívan használatban van könyv vagy fejezet alapértelmezett oldalsablonjaként. Ezekhez a könyvekhez vagy fejezetekhez a továbbiakban nem lesz alapértelmezett oldalsablon hozzárendelve az oldal törlése után.', - 'pages_delete_confirm' => 'Biztosan törölhető ez az oldal?', - 'pages_delete_draft_confirm' => 'Biztosan törölhető ez a vázlatoldal?', + 'pages_delete_warning_template' => 'Ez az oldal aktívan használatban van egy könyv vagy egy fejezet alapértelmezett oldalsablonjaként. Ezekhez a könyvekhez vagy fejezetekhez nem lesz alapértelmezett oldalsablon hozzárendelve az oldal törlése után.', + 'pages_delete_confirm' => 'Biztosan törli ezt az oldalt?', + 'pages_delete_draft_confirm' => 'Biztosan törli ezt az vázlat oldalt?', 'pages_editing_named' => ':pageName oldal szerkesztése', - 'pages_edit_draft_options' => 'Vázlatbeállítások', + 'pages_edit_draft_options' => 'Vázlat beállítások', 'pages_edit_save_draft' => 'Vázlat mentése', 'pages_edit_draft' => 'Oldal vázlat szerkesztése', 'pages_editing_draft' => 'Vázlat szerkesztése', 'pages_editing_page' => 'Oldal szerkesztése', - 'pages_edit_draft_save_at' => 'Vázlat elmentve:', + 'pages_edit_draft_save_at' => 'Vázlat elmentve: ', 'pages_edit_delete_draft' => 'Vázlat törlése', - 'pages_edit_delete_draft_confirm' => 'Biztos benne, hogy törölni kívánja az oldalmódosítások piszkozatát? Az utolsó teljes mentés óta végrehajtott összes módosítása elvész, és a szerkesztő frissül a legfrissebb, nem vázlatos mentési állapottal.', + 'pages_edit_delete_draft_confirm' => 'Biztos benne, hogy törli az oldalmódosítások vázlatát? Az utolsó teljes mentés óta végrehajtott összes módosítása elvész, és a szerkesztő frissül a legfrissebb, nem vázlatos mentési állapottal.', 'pages_edit_discard_draft' => 'Vázlat elvetése', 'pages_edit_switch_to_markdown' => 'Váltás Markdown szerkesztőre', - 'pages_edit_switch_to_markdown_clean' => '(Tisztított tartalom)', + 'pages_edit_switch_to_markdown_clean' => '(Tiszta tartalom)', 'pages_edit_switch_to_markdown_stable' => '(Stabil tartalom)', 'pages_edit_switch_to_wysiwyg' => 'Váltás a WYSIWYG szerkesztőre', - 'pages_edit_switch_to_new_wysiwyg' => 'Switch to new WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg' => 'Váltás az új WYSIWYG szerkeztőre', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(Béta tesztelés alatt)', 'pages_edit_set_changelog' => 'Változásnapló beállítása', 'pages_edit_enter_changelog_desc' => 'A végrehajtott módosítások rövid leírása', 'pages_edit_enter_changelog' => 'Változásnapló megadása', 'pages_editor_switch_title' => 'Szerkesztőváltás', 'pages_editor_switch_are_you_sure' => 'Biztosan módosítani szeretné ennek az oldalnak a szerkesztőjét?', - 'pages_editor_switch_consider_following' => 'A szerkesztők módosításakor vegye figyelembe a következőket:', - 'pages_editor_switch_consideration_a' => 'Mentés után az új szerkesztő opciót minden jövőbeli szerkesztő használni fogja, beleértve azokat is, amelyek esetleg nem tudják maguk módosítani a szerkesztő típusát.', + 'pages_editor_switch_consider_following' => 'A szerkesztők váltásakor vegye figyelembe a következőket:', + 'pages_editor_switch_consideration_a' => 'Mentés után az új szerkesztő opciót fogja minden jövőbeli szerkesztő használni, beleértve azokat is, akik esetleg nem tudják maguk módosítani a szerkesztő típusát.', 'pages_editor_switch_consideration_b' => 'Ez bizonyos körülmények között a részletek és a szintaxis elvesztéséhez vezethet.', 'pages_editor_switch_consideration_c' => 'A legutóbbi mentés óta végrehajtott címke- vagy változásnapló-módosítások nem maradnak fenn a módosítás során.', 'pages_save' => 'Oldal mentése', @@ -269,11 +269,11 @@ 'pages_md_editor' => 'Szerkesztő', 'pages_md_preview' => 'Előnézet', 'pages_md_insert_image' => 'Kép beillesztése', - 'pages_md_insert_link' => 'Entitás hivatkozás beillesztése', + 'pages_md_insert_link' => 'Belső hivatkozás beillesztése', 'pages_md_insert_drawing' => 'Rajz beillesztése', 'pages_md_show_preview' => 'Előnézet megjelenítése', 'pages_md_sync_scroll' => 'Előnézet pozíció szinkronizálása', - 'pages_md_plain_editor' => 'Plaintext editor', + 'pages_md_plain_editor' => 'Sima szöveg szerkesztő', 'pages_drawing_unsaved' => 'Nem mentett rajz található', 'pages_drawing_unsaved_confirm' => 'A rendszer nem mentett rajzadatokat talált egy korábbi sikertelen rajzmentési kísérletből. Szeretné visszaállítani és folytatni a nem mentett rajz szerkesztését?', 'pages_not_in_chapter' => 'Az oldal nincs fejezetben', @@ -285,7 +285,7 @@ 'pages_permissions_success' => 'Oldal jogosultságok frissítve', 'pages_revision' => 'Változat', 'pages_revisions' => 'Oldal változatai', - 'pages_revisions_desc' => 'Az alábbiakban az oldal összes korábbi verziója látható. Visszatekinthet, összehasonlíthatja és visszaállíthatja a régi oldalverziókat, ha az engedélyek lehetővé teszik. Előfordulhat, hogy az oldal teljes előzménye itt nem jelenik meg teljes mértékben, mivel a rendszerkonfigurációtól függően a régi változatok automatikusan törlődnek.', + 'pages_revisions_desc' => 'Az alábbiakban az oldal összes korábbi verziója látható. Visszanézheti, összehasonlíthatja és visszaállíthatja a régi verziókat, ha az engedélyek lehetővé teszik. Előfordulhat, hogy az oldal teljes előzménye itt nem jelenik meg, mivel a beállításoktól függően a régi változatok automatikusan törlődhetnek.', 'pages_revisions_named' => ':pageName oldal változatai', 'pages_revision_named' => ':pageName oldal változata', 'pages_revision_restored_from' => 'Visszaállítva innen: #:id; :summary', @@ -293,8 +293,8 @@ 'pages_revisions_date' => 'Változat dátuma', 'pages_revisions_number' => '#', 'pages_revisions_sort_number' => 'Változat száma', - 'pages_revisions_numbered' => 'Változat #:id', - 'pages_revisions_numbered_changes' => '#:id változat módosításai', + 'pages_revisions_numbered' => '#:id. változat', + 'pages_revisions_numbered_changes' => '#:id. változat módosításai', 'pages_revisions_editor' => 'Szerkesztő típusa', 'pages_revisions_changelog' => 'Változásnapló', 'pages_revisions_changes' => 'Módosítások', @@ -304,12 +304,12 @@ 'pages_revisions_none' => 'Ennek az oldalnak nincsenek változatai', 'pages_copy_link' => 'Hivatkozás másolása', 'pages_edit_content_link' => 'Ugrás a szakaszhoz a szerkesztőben', - 'pages_pointer_enter_mode' => 'Lépjen be a szakaszválasztó módba', + 'pages_pointer_enter_mode' => 'Belépés szakaszválasztó módba', 'pages_pointer_label' => 'Oldalszakasz beállításai', 'pages_pointer_permalink' => 'Oldalszakasz állandó hivatkozás', 'pages_pointer_include_tag' => 'Oldalszakasz tartalmazza a címkét', - 'pages_pointer_toggle_link' => 'Permalink mód, Nyomja meg az include tag megjelenítéséhez', - 'pages_pointer_toggle_include' => 'Include tag mód, Nyomja meg az permalink megjelenítéséhez', + 'pages_pointer_toggle_link' => 'Állandó hivatkozás mód. Nyomja meg az beágyazó címke megjelenítéséhez!', + 'pages_pointer_toggle_include' => 'Beágyazó címke mód. Nyomja meg az állandó hivatkozás megjelenítéséhez!', 'pages_permissions_active' => 'Oldal jogosultságok aktívak', 'pages_initial_revision' => 'Kezdeti közzététel', 'pages_references_update_revision' => 'A belső hivatkozások automatikus frissítése', @@ -318,36 +318,39 @@ 'pages_draft_edited_notification' => 'Ezt az oldalt azóta már frissítették. Javasolt ennek a vázlatnak az elvetése.', 'pages_draft_page_changed_since_creation' => 'Ez az oldal a vázlat létrehozása óta frissült. Javasoljuk, hogy dobja el ezt a piszkozatot, vagy ügyeljen arra, hogy ne írja felül az oldal módosításait.', 'pages_draft_edit_active' => [ - 'start_a' => ':count felhasználók kezdte el szerkeszteni ezt az oldalt', + 'start_a' => ':count felhasználó kezdte el szerkeszteni ezt az oldalt', 'start_b' => ':userName elkezdte szerkeszteni ezt az oldalt', 'time_a' => 'mióta az oldal utoljára frissítve volt', 'time_b' => 'az utolsó :minCount percben', - 'message' => ':start :time. Ügyeljen arra, hogy ne írjuk felül egymás frissítéseit!', + 'message' => ':start :time. Ügyeljen arra, hogy ne írja felül mások frissítéseit!', ], - 'pages_draft_discarded' => 'Vázlat elvetve! A szerkesztő frissítve lesz az oldal aktuális tartalmával', - 'pages_draft_deleted' => 'Vázlat elvetve! A szerkesztő frissítve lesz az oldal aktuális tartalmával', + 'pages_draft_discarded' => 'Vázlat elvetve! A szerkesztő frissítve lett az oldal aktuális tartalmával', + 'pages_draft_deleted' => 'Vázlat törölve! A szerkesztő frissítve lett az oldal aktuális tartalmával', 'pages_specific' => 'Egy bizonyos oldal', 'pages_is_template' => 'Oldalsablon', // Editor Sidebar 'toggle_sidebar' => 'Oldalsáv ki/be', + 'page_contents' => 'Oldal tartalma', + 'page_contents_none' => 'Nincsen egy címsor sem az oldalon.', + 'page_contents_info' => 'A tartalom menü automatikusan generálódik az oldalon használt címsorokból.', 'page_tags' => 'Oldal címkék', 'chapter_tags' => 'Fejezet címkék', 'book_tags' => 'Könyv címkék', 'shelf_tags' => 'Polc címkék', 'tag' => 'Címke', 'tags' => 'Címkék', - 'tags_index_desc' => 'A címkék a rendszeren belüli tartalomra alkalmazhatók a kategorizálás rugalmas formája alkalmazása érdekében. A címkéknek kulcsuk és értékük is lehetnek, de az érték nem kötelező. Alkalmazása után a tartalom lekérdezhető a címkenév és érték használatával.', + 'tags_index_desc' => 'A rendszeren belül a tartalomra címkék helyezhetők a rugalmasan rendezhetőség érdekében. A címkéknek neve és értéke is lehet, de az érték nem kötelező. Beállítás után a tartalom lekérdezhető a címkenév és érték használatával.', 'tag_name' => 'Címkenév', 'tag_value' => 'Címke érték (nem kötelező)', 'tags_explain' => "Címkék hozzáadása a tartalom jobb kategorizálásához.\nA mélyebb szervezettség megvalósításához hozzá lehet rendelni egy értéket a címkéhez.", 'tags_add' => 'Másik címke hozzáadása', 'tags_remove' => 'Címke eltávolítása', - 'tags_usages' => 'Összes címkehasználat', - 'tags_assigned_pages' => 'Oldalakhoz Rendelt', + 'tags_usages' => 'Összes címke használat', + 'tags_assigned_pages' => 'Oldalakhoz rendelt', 'tags_assigned_chapters' => 'Fejezetekhez rendelt', - 'tags_assigned_books' => 'Könyvekhez Rendelt', - 'tags_assigned_shelves' => 'Polcokhoz Rendelt', + 'tags_assigned_books' => 'Könyvekhez rendelt', + 'tags_assigned_shelves' => 'Polcokhoz rendelt', 'tags_x_unique_values' => ':count egyedi érték', 'tags_all_values' => 'Összes érték', 'tags_view_tags' => 'Címkék megtekintése', @@ -356,12 +359,12 @@ 'attachments' => 'Csatolmányok', 'attachments_explain' => 'Az oldalon megjelenő fájlok feltöltése vagy hivatkozások csatolása. Az oldal oldalsávjában fognak megjelenni.', 'attachments_explain_instant_save' => 'Az itt történt módosítások azonnal el lesznek mentve.', - 'attachments_upload' => 'Fájlfeltöltés', + 'attachments_upload' => 'Fáj lfeltöltése', 'attachments_link' => 'Hivatkozás csatolása', 'attachments_upload_drop' => 'Alternatív megoldásként a fájlt ide húzva is fel lehet tölteni mellékletként.', 'attachments_set_link' => 'Hivatkozás beállítása', - 'attachments_delete' => 'Biztosan törölhető ez a melléklet?', - 'attachments_dropzone' => 'Húzza a file(oka)t ide a feltöltéshez', + 'attachments_delete' => 'Biztosan törli a mellékletet?', + 'attachments_dropzone' => 'Húzza ide a fájlokat a feltöltéshez', 'attachments_no_files' => 'Nincsenek fájlok feltöltve', 'attachments_explain_link' => 'Fájl feltöltése helyett hozzá lehet kapcsolni egy hivatkozást. Ez egy hivatkozás lesz egy másik oldalra vagy egy fájlra a felhőben.', 'attachments_link_name' => 'Hivatkozás neve', @@ -369,12 +372,12 @@ 'attachments_link_url' => 'Hivatkozás fájlra', 'attachments_link_url_hint' => 'Weboldal vagy fájl webcíme', 'attach' => 'Csatolás', - 'attachments_insert_link' => 'Melléklet hivatkozás hozzáadása oldalhoz', + 'attachments_insert_link' => 'Melléklet hivatkozás hozzáadása az oldalhoz', 'attachments_edit_file' => 'Fájl szerkesztése', 'attachments_edit_file_name' => 'Fájl neve', 'attachments_edit_drop_upload' => 'Feltöltés és felülírás ejtéssel vagy kattintással', 'attachments_order_updated' => 'Csatolmány sorrend frissítve', - 'attachments_updated_success' => 'Csatolmány részletei frissítve', + 'attachments_updated_success' => 'Csatolmány részletek frissítve', 'attachments_deleted' => 'Csatolmány törölve', 'attachments_file_uploaded' => 'Fájl sikeresen feltöltve', 'attachments_file_updated' => 'Fájl sikeresen frissítve', @@ -387,7 +390,7 @@ 'templates_prepend_content' => 'Hozzáadás az oldal tartalmának elejéhez', // Profile View - 'profile_user_for_x' => 'Felhasználó ez óta: :time', + 'profile_user_for_x' => 'Felhasználó ennyi ideje: :time', 'profile_created_content' => 'Létrehozott tartalom', 'profile_not_created_pages' => ':userName még nem hozott létre oldalt', 'profile_not_created_chapters' => ':userName még nem hozott létre fejezetet', @@ -395,61 +398,61 @@ 'profile_not_created_shelves' => ':userName még nem hozott létre polcot', // Comments - 'comment' => 'Megjegyzés', - 'comments' => 'Megjegyzések', - 'comment_add' => 'Megjegyzés hozzáadása', - 'comment_none' => 'No comments to display', - 'comment_placeholder' => 'Megjegyzés írása', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', - 'comment_save' => 'Megjegyzés mentése', - 'comment_new' => 'Új megjegyzés', - 'comment_created' => 'megjegyzést fűzött hozzá :createDiff', + 'comment' => 'Hozzászólás', + 'comments' => 'Hozzászólások', + 'comment_add' => 'Hozzászólás hozzáadása', + 'comment_none' => 'Nincs megjeleníthető hozzászólás', + 'comment_placeholder' => 'Hozzászólás írása', + 'comment_thread_count' => ':count hozzászóláslánc|:count hozzászóláslánc', + 'comment_archived_count' => ':count archivált', + 'comment_archived_threads' => 'Archivált hozzászólásláncok', + 'comment_save' => 'Hozzászólás mentése', + 'comment_new' => 'Új hozzászólás', + 'comment_created' => 'hozzászólt :createDiff', 'comment_updated' => 'Frissítve :updateDiff :username által', 'comment_updated_indicator' => 'Frissített', - 'comment_deleted_success' => 'Megjegyzés törölve', - 'comment_created_success' => 'Megjegyzés hozzáadva', - 'comment_updated_success' => 'Megjegyzés frissítve', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', - 'comment_delete_confirm' => 'Biztosan törölhető ez a megjegyzés?', - 'comment_in_reply_to' => 'Válasz erre: :commentId', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', - 'comment_editor_explain' => 'Itt vannak az ezen az oldalon lévő megjegyzések. Megjegyzések hozzáadhatók és kezelhetők a mentett oldal megtekintésekor.', + 'comment_deleted_success' => 'Hozzászólás törölve', + 'comment_created_success' => 'Hozzászólás hozzáadva', + 'comment_updated_success' => 'Hozzászólás frissítve', + 'comment_archive_success' => 'Hozzászólás archiválva', + 'comment_unarchive_success' => 'Hozzászólás visszaállítva', + 'comment_view' => 'Hozzászólás megjelenítése', + 'comment_jump_to_thread' => 'Ugrás hozzászóláslánchoz', + 'comment_delete_confirm' => 'Biztosan törli a megjegyzést?', + 'comment_in_reply_to' => 'Válaszolva erre: :commentId', + 'comment_reference' => 'Hivatkozás', + 'comment_reference_outdated' => '(Elavult)', + 'comment_editor_explain' => 'Itt vannak az ezen az oldalon lévő hozzászólások. Hozzászólás hozzáadhatók és kezelhetők a mentett oldal megtekintésekor.', // Revision - 'revision_delete_confirm' => 'Biztosan törölhető ez a változat?', - 'revision_restore_confirm' => 'Biztosan visszaállítható ez a változat? A oldal jelenlegi tartalma le lesz cserélve.', - 'revision_cannot_delete_latest' => 'A legutolsó változat nem törölhető.', + 'revision_delete_confirm' => 'Biztosan törli ezt a változatot?', + 'revision_restore_confirm' => 'Biztosan visszaállítja ezt a változatot? A oldal jelenlegi tartalma le lesz cserélve.', + 'revision_cannot_delete_latest' => 'A legutóbbi változat nem törölhető.', // Copy view 'copy_consider' => 'Kérem, fontolja meg az alábbiakat, amikor tartalmat kíván másolni.', - 'copy_consider_permissions' => 'Az egyéni engedélybeállítások nem kerülnek másolásra.', + 'copy_consider_permissions' => 'Az egyedi engedély beállítások nem kerülnek másolásra.', 'copy_consider_owner' => 'Minden lemásolt tartalomnak Ön lesz a tulajdonosa.', - 'copy_consider_images' => 'Az oldalképfájlok nem duplikálódnak, és az eredeti képek megőrzik kapcsolatukat az eredetileg feltöltött oldallal.', - 'copy_consider_attachments' => 'Az oldal mellékletei nem kerülnek másolásra.', - 'copy_consider_access' => 'A change of location, owner or permissions may result in this content being accessible to those previously without access.', + 'copy_consider_images' => 'Az kép fájlok nem duplikálódnak, és az eredeti képek megőrzik kapcsolatukat az oldallal, ahova eredetileg fel lettek töltve.', + 'copy_consider_attachments' => 'Az oldal mellékletei nem lesznek lemásolva.', + 'copy_consider_access' => 'A hely, tulajdonos vagy jogosultságok megváltoztatása olyanoknak is hozzáférést adhat a tartalomhoz, akik korábban nem érhették el.', // Conversions 'convert_to_shelf' => 'Átalakítás polccá', - 'convert_to_shelf_contents_desc' => 'Ezt a könyvet új polccá alakíthatja, azonos tartalommal. A könyvben található fejezetek új könyvekké lesznek átalakítva. Ha ez a könyv tartalmaz olyan oldalakat, amelyek nem szerepelnek egy fejezetben, akkor a könyv átnevezzük és tartalmaz ilyen oldalakat, és ez a könyv az új polc részévé válik.', - 'convert_to_shelf_permissions_desc' => 'A könyvhöz beállított engedélyek át lesznek másolva az új polcra és az összes olyan új alárendelt könyvre, amelyek nem rendelkeznek saját engedélyekkel. Vegye figyelembe, hogy a polcokon lévő engedélyek nem kapcsolódnak automatikusan a tartalomhoz, ahogy a könyvek esetében.', + 'convert_to_shelf_contents_desc' => 'Ezt a könyvet új polccá alakíthatja, azonos tartalommal. A könyvben található fejezetek új könyvekké lesznek átalakítva. Ha ez a könyv tartalmaz olyan oldalakat, amelyek nem szerepelnek egy fejezetben, akkor a könyv át lesz nevezve, az oldalak hozzárendelve és ez a könyv az új polc részévé válik.', + 'convert_to_shelf_permissions_desc' => 'A könyvhöz beállított engedélyek át lesznek másolva az új polcra és az összes olyan új alárendelt könyvre, amelyek nem rendelkeznek saját engedélyekkel. Vegye figyelembe, hogy a polcokon lévő engedélyek nem vonatkoznak automatikusan az alárendelt tartalomra, ahogy a könyvek esetében.', 'convert_book' => 'Könyv átalakítása', - 'convert_book_confirm' => 'Biztosan konvertálni szeretné ezt a könyvet?', + 'convert_book_confirm' => 'Biztosan át szeretné alakítani ezt a könyvet?', 'convert_undo_warning' => 'Ezt nem lehet olyan könnyen visszavonni.', 'convert_to_book' => 'Átalakítás könyvvé', 'convert_to_book_desc' => 'Ezt a fejezetet új, azonos tartalmú könyvvé alakíthatja. Az ebben a fejezetben beállított engedélyek átmásolódnak az új könyvbe, de a szülőkönyvből származó örökölt engedélyek nem kerülnek másolásra, ami a hozzáférés-szabályozás megváltozásához vezethet.', 'convert_chapter' => 'Fejezet átalakítása', - 'convert_chapter_confirm' => 'Biztosan át szeretnéd alakítani ezt a fejezetet?', + 'convert_chapter_confirm' => 'Biztosan át szeretné alakítani ezt a fejezetet?', // References - 'references' => 'Értékelések', + 'references' => 'Hivatkozások', 'references_none' => 'Nincsenek nyomon követett hivatkozások erre az elemre.', - 'references_to_desc' => 'Az alábbiakban felsoroljuk az összes ismert tartalmat a rendszerben, amely erre az elemre hivatkozik.', + 'references_to_desc' => 'Alább fel van sorolva az összes ismert tartalom a rendszerben, amely erre az elemre hivatkozik.', // Watch Options 'watch' => 'Megfigyelés', @@ -459,19 +462,19 @@ 'watch_desc_ignore' => 'Figyelmen kívül hagyja az összes értesítést, beleértve a felhasználói szintű beállításokból származó értesítéseket is.', 'watch_title_new' => 'Új oldalak', 'watch_desc_new' => 'Értesítés, ha új oldal jön létre ezen az elemen belül.', - 'watch_title_updates' => 'Minden oldal frissítése', - 'watch_desc_updates' => 'Értesítés minden új oldalról és oldalváltozásról.', - 'watch_desc_updates_page' => 'Értesítsen minden oldalváltozásról.', - 'watch_title_comments' => 'Az oldal összes frissítése és megjegyzése', - 'watch_desc_comments' => 'Értesítés minden új oldalról, oldalváltozásról és új megjegyzésről.', - 'watch_desc_comments_page' => 'Értesítés az oldal változásairól és az új megjegyzésekről.', + 'watch_title_updates' => 'Minden oldal változás', + 'watch_desc_updates' => 'Értesítés minden új oldalról és oldal változásról.', + 'watch_desc_updates_page' => 'Értesítsen minden oldal változásról.', + 'watch_title_comments' => 'Minden oldal változás és hozzászólás', + 'watch_desc_comments' => 'Értesítés minden új oldalról, oldal változásról és új hozzászólásról.', + 'watch_desc_comments_page' => 'Értesítés az oldal változásairól és az új hozzászólásokról.', 'watch_change_default' => 'Az alapértelmezett értesítési beállítások módosítása', 'watch_detail_ignore' => 'Az értesítések figyelmen kívül hagyása', 'watch_detail_new' => 'Új oldalak figyelése', - 'watch_detail_updates' => 'Új oldalak és frissítések figyelése', - 'watch_detail_comments' => 'Új oldalak, frissítések és megjegyzések figyelése', - 'watch_detail_parent_book' => 'Megfigyelés szülőkönyvből', - 'watch_detail_parent_book_ignore' => 'Figyelmen kívül hagyás a szülőkönyvön keresztül', - 'watch_detail_parent_chapter' => 'Megfigyelés szülő fejezetből', - 'watch_detail_parent_chapter_ignore' => 'Figyelmen kívül hagyás a szülő fejezeten keresztül', + 'watch_detail_updates' => 'Új oldalak és változások figyelése', + 'watch_detail_comments' => 'Új oldalak, változások és hozzászólások figyelése', + 'watch_detail_parent_book' => 'Megfigyelés tartalmazó könyvön keresztül', + 'watch_detail_parent_book_ignore' => 'Figyelmen kívül hagyás tartalmazó könyvön keresztül', + 'watch_detail_parent_chapter' => 'Megfigyelés tartalmazó fejezetten keresztül', + 'watch_detail_parent_chapter_ignore' => 'Figyelmen kívül hagyás tartalmazó fejezetten keresztül', ]; diff --git a/lang/hu/errors.php b/lang/hu/errors.php index 4264af8ab67..c3b1a4d6d53 100644 --- a/lang/hu/errors.php +++ b/lang/hu/errors.php @@ -6,46 +6,46 @@ // Permissions 'permission' => 'Nincs jogosultság a kért oldal eléréséhez.', - 'permissionJson' => 'Nincs jogosultság a kért művelet végrehajtásához.', + 'permissionJson' => 'Nincs jogosultsága a kért művelet végrehajtásához.', // Auth 'error_user_exists_different_creds' => ':email címmel már létezik felhasználó, de más hitelesítő adatokkal.', 'auth_pre_register_theme_prevention' => 'A felhasználói fiók nem regisztrálható a megadott adatokkal', - 'email_already_confirmed' => 'Az email cím már meg van erősítve, meg lehet próbálni a bejelentkezést.', - 'email_confirmation_invalid' => 'A megerősítő vezérjel nem érvényes vagy használva volt. Meg kell próbálni újraregisztrálni.', - 'email_confirmation_expired' => 'A megerősítő vezérjel lejárt. Egy új megerősítő email lett elküldve.', + 'email_already_confirmed' => 'Az email cím már meg van erősítve. Próbáljon meg bejelentkezni!', + 'email_confirmation_invalid' => 'Ez a megerősítő kulcs nem érvényes vagy használva már volt. Próbáljon meg újra regisztrálni!', + 'email_confirmation_expired' => 'Ez a megerősítő kulcs már lejárt. Egy új megerősítő email lett küldve.', 'email_confirmation_awaiting' => 'A használatban lévő fiók email címét meg kell erősíteni', 'ldap_fail_anonymous' => 'Nem sikerült az LDAP elérése névtelen csatlakozással', - 'ldap_fail_authed' => 'Az LDAP hozzáférés nem sikerült a megadott DN és jelszó beállításokkal', - 'ldap_extension_not_installed' => 'LDAP PHP kiterjesztés nincs telepítve', - 'ldap_cannot_connect' => 'Nem lehet kapcsolódni az LDAP kiszolgálóhoz, a kezdeti kapcsolatfelvétel nem sikerült', - 'saml_already_logged_in' => 'Már bejelentkezett', + 'ldap_fail_authed' => 'Nem sikerült az LDAP elérése a megadott DN és jelszó adatokkal', + 'ldap_extension_not_installed' => 'Az LDAP PHP kiterjesztés nincsen telepítve', + 'ldap_cannot_connect' => 'Nem lehet kapcsolódni az LDAP szerverhez, a kezdeti kapcsolatfelvétel nem sikerült', + 'saml_already_logged_in' => 'Már be van jelentkezve', 'saml_no_email_address' => 'Ehhez a felhasználóhoz nem található email cím a külső hitelesítő rendszer által átadott adatokban', - 'saml_invalid_response_id' => 'A külső hitelesítő rendszerből érkező kérést nem ismerte fel az alkalmazás által indított folyamat. Bejelentkezés után az előző oldalra történő visszalépés okozhatja ezt a hibát.', - 'saml_fail_authed' => 'Bejelentkezés :system használatával sikertelen, a rendszer nem biztosított sikeres hitelesítést', - 'oidc_already_logged_in' => 'Már bejelentkezett', + 'saml_invalid_response_id' => 'A külső hitelesítő rendszerből érkező kérést nem ismerte fel egy az alkalmazás által indított folyamat sem. Bejelentkezés után az előző oldalra történő visszalépés okozhatja ezt a hibát.', + 'saml_fail_authed' => 'A bejelentkezés a(z) :system használatával sikertelen volt, a rendszer nem biztosított sikeres hitelesítést', + 'oidc_already_logged_in' => 'Már be van jelentkezve', 'oidc_no_email_address' => 'Ehhez a felhasználóhoz nem található email cím a külső hitelesítő rendszer által átadott adatokban', - 'oidc_fail_authed' => 'Bejelentkezés :system használatával sikertelen, a rendszer nem biztosított sikeres hitelesítést', - 'social_no_action_defined' => 'Nincs művelet meghatározva', - 'social_login_bad_response' => "Hiba történt :socialAccount bejelentkezés közben:\n:error", - 'social_account_in_use' => ':socialAccount fiók már használatban van. :socialAccount opción keresztül érdemes megpróbálni a bejelentkezést.', - 'social_account_email_in_use' => ':email email cím már használatban van. Ha már van fiók létrehozva, :egy socialAccount fiókot hozzá lehet csatolni a profil beállításainál.', - 'social_account_existing' => ':socialAccount már hozzá van kapcsolva a fiókhoz.', - 'social_account_already_used_existing' => ':socialAccount fiókot már egy másik felhasználó használja.', - 'social_account_not_used' => ':socialAccount fiók nincs felhasználóhoz kapcsolva. A hozzákapcsolást a profil oldalon lehet elvégezni. ', - 'social_account_register_instructions' => ':socialAccount beállítása használatával is lehet fiókot regisztrálni, ha még nem volt fiók létrehozva.', - 'social_driver_not_found' => 'Közösségi meghajtó nem található', - 'social_driver_not_configured' => ':socialAccount közösségi beállítások nem megfelelőek.', - 'invite_token_expired' => 'Ez a meghívó hivatkozás lejárt. Helyette meg lehet próbálni új jelszót megadni a fiókhoz.', + 'oidc_fail_authed' => 'A bejelentkezés a(z) :system használatával sikertelen volt, a rendszer nem biztosított sikeres hitelesítést', + 'social_no_action_defined' => 'Nincs meghatározott művelet', + 'social_login_bad_response' => "Hiba történt a(z) :socialAccount bejelentkezés közben:\n:error", + 'social_account_in_use' => 'Ez a(z) :socialAccount fiók már használatban van. Próbáljon meg bejelentkezni a(z) :socialAccount opción keresztül!', + 'social_account_email_in_use' => 'A(z) :email email cím már használatban van. Ha már van fiókja, hozzá tudja kapcsolni a(z) :socialAccount fiókját a profil beállításainál.', + 'social_account_existing' => 'Ez a(z) :socialAccount már hozzá van kapcsolva a fiókjához.', + 'social_account_already_used_existing' => 'Ezt a(z) :socialAccount fiókot egy másik felhasználó már használja.', + 'social_account_not_used' => 'Ez a(z) :socialAccount fiók nincs egy felhasználóhoz sem kapcsolva. Kapcsolja hozzá a profil beállításoknál. ', + 'social_account_register_instructions' => 'Ha még nincsen fiókja, regisztrálhat egyet a(z) :socialAccount opció használatával.', + 'social_driver_not_found' => 'Nem található közösségi média illesztőprogram', + 'social_driver_not_configured' => 'A(z) :socialAccount fiók összekapcsolás beállításai nem megfelelőek.', + 'invite_token_expired' => 'Ez a meghívó már lejárt. Helyette megpróbálhatja a fiók jelszavát visszaállítani.', 'login_user_not_found' => 'A művelethez nem található felhasználó.', // System - 'path_not_writable' => ':filePath elérési út nem tölthető fel. Ellenőrizni kell, hogy az útvonal a kiszolgáló számára írható.', - 'cannot_get_image_from_url' => 'Nem lehet lekérni a képet innen: :url', - 'cannot_create_thumbs' => 'A kiszolgáló nem tud létrehozni bélyegképeket. Ellenőrizni kell, hogy telepítve van-a a GD PHP kiterjesztés.', - 'server_upload_limit' => 'A kiszolgáló nem engedélyez ilyen méretű feltöltéseket. Kisebb fájlmérettel kell próbálkozni.', - 'server_post_limit' => 'A szerver nem tudja fogadni a megadott adatmennyiséget. Próbálkozz újra kevesebb adattal vagy egy kisebb fájllal.', - 'uploaded' => 'A kiszolgáló nem engedélyez ilyen méretű feltöltéseket. Kisebb fájlmérettel kell próbálkozni.', + 'path_not_writable' => 'Nem sikerült feltölteni a :filePath elérési útra. Ellenőrizze, hogy az útvonal írható a szerveren!', + 'cannot_get_image_from_url' => 'Nem lehet lekérni a képet a(z) :url címről', + 'cannot_create_thumbs' => 'A szerver nem tud létrehozni bélyegképeket. Ellenőrizze, hogy telepítve van a GD PHP kiterjesztés!', + 'server_upload_limit' => 'A szerver nem engedélyez ekkora méretű feltöltéseket. Kérjük próbálkozzon kisebb fájl mérettel.', + 'server_post_limit' => 'A szerver nem tudja fogadni a megadott adatmennyiséget. Próbálkozzon újra kevesebb adattal vagy egy kisebb fájllal!', + 'uploaded' => 'A szerver nem engedélyez ekkora méretű feltöltéseket. Kérjük próbálkozzon kisebb fájl mérettel.', // Drawing & Images 'image_upload_error' => 'Hiba történt a kép feltöltése közben', @@ -78,7 +78,7 @@ // Users 'users_cannot_delete_only_admin' => 'Nem lehet törölni az egyetlen adminisztrátort', 'users_cannot_delete_guest' => 'A vendég felhasználót nem lehet törölni', - 'users_could_not_send_invite' => 'Could not create user since invite email failed to send', + 'users_could_not_send_invite' => 'Nem lehetett létrehozni a felhasználót, mivel a meghívó levelet nem sikerült elküldeni', // Roles 'role_cannot_be_edited' => 'Ezt a szerepkört nem lehet szerkeszteni', @@ -87,16 +87,16 @@ 'role_cannot_remove_only_admin' => 'Ez a felhasználó az egyetlen, az adminisztrátor szerepkörhöz rendelt felhasználó. Eltávolítása előtt az adminisztrátor szerepkört át kell ruházni egy másik felhasználóra.', // Comments - 'comment_list' => 'Hiba történt a megjegyzések lekérése közben.', - 'cannot_add_comment_to_draft' => 'Vázlathoz nem lehet megjegyzéseket fűzni.', - 'comment_add' => 'Hiba történt a megjegyzés hozzáadása / frissítése közben.', - 'comment_delete' => 'Hiba történt a megjegyzés törlése közben.', - 'empty_comment' => 'Üres megjegyzést nem lehet hozzáadni.', + 'comment_list' => 'Hiba történt a hozzászólások lekérése közben.', + 'cannot_add_comment_to_draft' => 'Vázlathoz nem lehet hozzászólni.', + 'comment_add' => 'Hiba történt a hozzászólás hozzáadása/frissítése közben.', + 'comment_delete' => 'Hiba történt a hozzászólás törlése közben.', + 'empty_comment' => 'Üres hozzászólást nem lehet hozzáadni.', // Error pages - '404_page_not_found' => 'Oldal nem található', + '404_page_not_found' => 'Az oldal nem található', 'sorry_page_not_found' => 'Sajnáljuk, a keresett oldal nem található.', - 'sorry_page_not_found_permission_warning' => 'Ha arra számított, hogy ez az oldal létezik, előfordulhat, hogy nincs engedélye a megtekintésére.', + 'sorry_page_not_found_permission_warning' => 'Ha arra számított, hogy ez az oldal létezik, előfordulhat, hogy nincs jogosultsága a megtekintésére.', 'image_not_found' => 'A kép nem található', 'image_not_found_subtitle' => 'Sajnáljuk, a keresett kép nem található.', 'image_not_found_details' => 'Ha arra számított, hogy ez a kép létezik, akkor előfordulhat, hogy törölték.', @@ -106,26 +106,26 @@ 'back_soon' => 'Hamarosan újra elérhető lesz.', // Import - 'import_zip_cant_read' => 'Could not read ZIP file.', - 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', - 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', - 'import_validation_failed' => 'Import ZIP failed to validate with errors:', - 'import_zip_failed_notification' => 'Failed to import ZIP file.', - 'import_perms_books' => 'You are lacking the required permissions to create books.', - 'import_perms_chapters' => 'You are lacking the required permissions to create chapters.', - 'import_perms_pages' => 'You are lacking the required permissions to create pages.', - 'import_perms_images' => 'You are lacking the required permissions to create images.', - 'import_perms_attachments' => 'You are lacking the required permission to create attachments.', + 'import_zip_cant_read' => 'Nem sikerült a ZIP fájlt olvasni.', + 'import_zip_cant_decode_data' => 'Nem sikerült megtalálni és dekódolni a data.json tartalmát a ZIP fájlban.', + 'import_zip_no_data' => 'A ZIP fájlban nincsen könyv, fejezet vagy oldal tartalom.', + 'import_zip_data_too_large' => 'A ZIP fájlban lévő data.json tartalma meghaladja a beállított feltöltési méret limitet.', + 'import_validation_failed' => 'A ZIP fájl importálásának ellenőrzése sikertelen volt a következő hibák miatt:', + 'import_zip_failed_notification' => 'Nem sikerült a ZIP fájlt importálni.', + 'import_perms_books' => 'Nem rendelkezik a könyvek létrehozásához szükséges jogosultságokkal.', + 'import_perms_chapters' => 'Nem rendelkezik a fejezetek létrehozásához szükséges jogosultságokkal.', + 'import_perms_pages' => 'Nem rendelkezik az oldalak létrehozásához szükséges jogosultságokkal.', + 'import_perms_images' => 'Nem rendelkezik a képek létrehozásához szükséges jogosultságokkal.', + 'import_perms_attachments' => 'Nem rendelkezik a csatolmányok létrehozásához szükséges jogosultságokkal.', // API errors - 'api_no_authorization_found' => 'A kérésben nem található hitelesítési vezérjel', - 'api_bad_authorization_format' => 'A kérésben hitelesítési vezérjel található de a formátuma érvénytelennek tűnik', - 'api_user_token_not_found' => 'A megadott hitelesítési vezérjelhez nem található egyező API vezérjel', - 'api_incorrect_token_secret' => 'Az API tokenhez használt secret helytelen', - 'api_user_no_api_permission' => 'A használt API vezérjel tulajdonosának nincs jogosultsága API hívások végrehajtásához', - 'api_user_token_expired' => 'A használt hitelesítési vezérjel lejárt', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_no_authorization_found' => 'A kérésben nem található hitelesítési kulcs', + 'api_bad_authorization_format' => 'A kérésben hitelesítési kulcs található de a formátuma érvénytelennek tűnik', + 'api_user_token_not_found' => 'A megadott hitelesítési kulcshoz nem található egyező API kulcs', + 'api_incorrect_token_secret' => 'Az API kulcshoz megadott jelkulcs helytelen', + 'api_user_no_api_permission' => 'A használt API kulcs tulajdonosának nincs jogosultsága API hívások végrehajtásához', + 'api_user_token_expired' => 'A használt hitelesítési kulcs lejárt', + 'api_cookie_auth_only_get' => 'Kizárólag GET kérések engedélyezettek az API-n keresztül süti alapú authentikáció használatkor', // Settings & Maintenance 'maintenance_test_email_failure' => 'Hiba történt egy teszt email küldésekor:', diff --git a/lang/hu/notifications.php b/lang/hu/notifications.php index d8a29688a13..e7073a4967b 100644 --- a/lang/hu/notifications.php +++ b/lang/hu/notifications.php @@ -4,26 +4,26 @@ */ return [ - 'new_comment_subject' => 'Új megjegyzés ezen az oldalon: :pageName', - 'new_comment_intro' => 'Egy felhasználó hozzászólt egy oldalon itt: :appName:', + 'new_comment_subject' => 'Új megjegyzés a(z) :pageName oldalon', + 'new_comment_intro' => 'Egy felhasználó hozzászólt egy oldalon a(z) :appName: alkalmazásban', 'new_page_subject' => 'Új oldal: :pageName', - 'new_page_intro' => 'Az új oldal létrehozása sikeres volt itt: :appName:', + 'new_page_intro' => 'Egy új oldal lett létrehozva a(z) :appName: alkalmazásban', 'updated_page_subject' => 'Frissített oldal: :pageName', 'updated_page_intro' => 'Az oldal frissítése sikeres volt itt: :appName:', - 'updated_page_debounce' => 'Az értesítések tömegének elkerülése érdekében egy ideig nem kap értesítést az oldal további szerkesztéseiről ugyanaz a szerkesztő.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'updated_page_debounce' => 'Az értesítések tömegének elkerülése érdekében egy ideig nem kap értesítést az oldal további szerkesztéseiről ugyanattól a szerkesztőtől.', + 'comment_mention_subject' => 'Ön meg lett említve a(z) :pageName oldalon', + 'comment_mention_intro' => 'Ön meg lett említve egy hozzászólásban a(z) :appName: alkalmazásban', 'detail_page_name' => 'Oldal neve:', 'detail_page_path' => 'Oldal helye:', 'detail_commenter' => 'Hozzászóló:', - 'detail_comment' => 'Megjegyzés:', + 'detail_comment' => 'Hozzászólás:', 'detail_created_by' => 'Készítette:', 'detail_updated_by' => 'Frissítette:', 'action_view_comment' => 'Hozzászólás megtekintése', 'action_view_page' => 'Oldal megtekintése', - 'footer_reason' => 'Ezt az értesítést azért küldtük, mert a :link lefedi ezt a tevékenységtípust ehhez az elemhez.', - 'footer_reason_link' => 'értesítési beállításait', + 'footer_reason' => 'Ezt az értesítést azért küldtük, mert a :link lefedi ezt a tevékenység típust ehhez az elemhez.', + 'footer_reason_link' => 'az Ön értesítési beállításai', ]; diff --git a/lang/hu/passwords.php b/lang/hu/passwords.php index 03a37684ec6..90b13c30f7f 100644 --- a/lang/hu/passwords.php +++ b/lang/hu/passwords.php @@ -7,9 +7,9 @@ return [ 'password' => 'A jelszónak legalább hat karakterből kell állnia, és egyeznie kell a megerősítéssel.', - 'user' => "Nem található felhasználó ezzel az e-mail címmel.", - 'token' => 'A jelszó visszaállító biztonsági kód nem érvényes ehhez az e-mail címhez.', - 'sent' => 'E-mailben elküldtük a jelszó visszaállító hivatkozást!', + 'user' => "Nem található felhasználó ezzel az email címmel.", + 'token' => 'A jelszó visszaállító kulcs nem érvényes ehhez az email címhez.', + 'sent' => 'Emailben elküldtük a jelszó visszaállító hivatkozást!', 'reset' => 'A jelszó visszaállítva!', ]; diff --git a/lang/hu/preferences.php b/lang/hu/preferences.php index fb3c335e8ae..735e1fab7e3 100644 --- a/lang/hu/preferences.php +++ b/lang/hu/preferences.php @@ -15,38 +15,38 @@ 'shortcuts_section_navigation' => 'Navigáció', 'shortcuts_section_actions' => 'Gyakori műveletek', 'shortcuts_save' => 'Billentyűparancsok mentése', - 'shortcuts_overlay_desc' => 'Megjegyzés: Amikor a gyorsbillentyűk engedélyezve vannak, egy segítő átfedés érhető el azzal, hogy a "?" billentyűt megnyomva kiemeli az aktuálisan látható képernyőn elérhető gyorsbillentyűket a műveletekhez.', - 'shortcuts_update_success' => 'A gyorsbillentyű-beállítások frissítve lettek!', - 'shortcuts_overview_desc' => 'A rendszerfelhasználói felületen történő navigálásához használható billentyűparancsok kezelése.', + 'shortcuts_overlay_desc' => 'Megjegyzés: Amikor a gyorsbillentyűk engedélyezve vannak, a súgó ablak elérhető azzal, hogy a "?" billentyűt megnyomva kiemeli az aktuálisan látható képernyőn elérhető gyorsbillentyűket a műveletekhez.', + 'shortcuts_update_success' => 'A gyorsbillentyű beállítások frissítve lettek!', + 'shortcuts_overview_desc' => 'A felhasználói felületen történő navigálásához használható billentyűparancsok kezelése.', 'notifications' => 'Értesítési beállítások', - 'notifications_desc' => 'Állítsd be az e-mail értesítéseket, amelyeket akkor kapsz, ha bizonyos tevékenység történik a rendszeren belül.', - 'notifications_opt_own_page_changes' => 'Értesítsen változásokról az általam tulajdonolt oldalakon', - 'notifications_opt_own_page_comments' => 'Értesítés a hozzászólásokról az általam tulajdonolt oldalakon', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_desc' => 'A rendszerben folytatott tevékenységekről kapott értesítések beállítása.', + 'notifications_opt_own_page_changes' => 'Értesítsen változásokról az általam birtokolt oldalakon', + 'notifications_opt_own_page_comments' => 'Értesítés a hozzászólásokról az általam birtokolt oldalakon', + 'notifications_opt_comment_mentions' => 'Értesítsen, amikor megemlítenek egy hozzászólásban', 'notifications_opt_comment_replies' => 'Értesítsen válaszokról a hozzászólásaimra', 'notifications_save' => 'Beállítások mentése', 'notifications_update_success' => 'Az értesítési beállítások frissítve lettek!', 'notifications_watched' => 'Megfigyelt és figyelmen kívül hagyott elemek', 'notifications_watched_desc' => 'Az alábbi elemekre egyedi figyelési beállítások vannak alkalmazva. A beállítások frissítéséhez tekintsd meg az elemet, majd keresd a figyelési lehetőségeket az oldalsávban.', - 'auth' => 'Hozzáférés és Biztonság', + 'auth' => 'Hozzáférés és biztonság', 'auth_change_password' => 'Jelszó módosítása', - 'auth_change_password_desc' => 'Változtasd meg az alkalmazásba történő bejelentkezéshez használt jelszavadat. Ennek legalább 8 karakter hosszúnak kell lennie.', + 'auth_change_password_desc' => 'A bejelentkezéshez használt jelszó módosítása. Legalább 8 karakter hosszúnak kell lennie.', 'auth_change_password_success' => 'A jelszó frissítve lett!', 'profile' => 'Felhasználó részletei', 'profile_desc' => 'A kommunikációhoz és a rendszer személyre szabásához használt adatokon kívül kezelheti fiókja adatait, amelyek más felhasználók számára jelennek meg.', 'profile_view_public' => 'Nyilvános profil megtekintése', - 'profile_name_desc' => 'Állítsd be a megjelenített nevedet, amely látható lesz a rendszer többi felhasználója számára az általad végzett tevékenység és a saját tartalom révén.', - 'profile_email_desc' => 'Ezt az e-mail címet értesítésekre fogjuk használni, valamint az érvényben lévő beállítások függvényében hitelesítéshez is.', - 'profile_email_no_permission' => 'Sajnos nincs jogosultságod az e-mail cím megváltoztatására. Ha szeretnéd ezt megváltoztatni, kérj meg egy adminisztrátort, hogy ezt megtegye helyetted.', - 'profile_avatar_desc' => 'Válassz egy képet, amelyet a rendszerben használnál a neved mellett. A kép lehetőleg négyzet alakú és körülbelül 256px szélességű és magasságú legyen.', + 'profile_name_desc' => 'Állítsa be a megjelenített nevét amely látható lesz a rendszer többi felhasználója számára az Ön által végzett tevékenység és a saját tartalom révén.', + 'profile_email_desc' => 'Ezt az email címet értesítésekre fogjuk használni, valamint az érvényben lévő beállítások függvényében hitelesítéshez is.', + 'profile_email_no_permission' => 'Sajnos nincs jogosultsága az email cím megváltoztatására. Ha szeretné ezt megváltoztatni, kérjen meg egy adminisztrátort, hogy ezt megtegye helyette.', + 'profile_avatar_desc' => 'Válasszon egy képet, amelyet a rendszerben használni szeretne a neve mellett. A kép lehetőleg négyzet alakú és körülbelül 256 pixel szélességű és magasságú legyen.', 'profile_admin_options' => 'Adminisztrátori beállítások', - 'profile_admin_options_desc' => 'További adminisztrátori szintű lehetőségek, például a szerepkörök hozzárendelésének kezelése, megtalálhatóak a felhasználói fiókod beállításai között az "Beállítások > Felhasználók" területen az alkalmazásban.', + 'profile_admin_options_desc' => 'További adminisztrátori szintű lehetőségek, például a szerepkörök hozzárendelésének kezelése, megtalálhatók a felhasználói fiókod beállításai között az "Beállítások > Felhasználók" területen az alkalmazásban.', 'delete_account' => 'Felhasználói fiók törlése', - 'delete_my_account' => 'Törlöm a felhasználói fiókomat', - 'delete_my_account_desc' => 'Ez véglegesen törölni fogja a felhasználói fiókodat a rendszerből. Nem lesz lehetőséged visszaállítani ezt a fiókot, vagy visszavonni ezt a műveletet. A létrehozott tartalmak, például az oldalak és feltöltött képek megmaradnak.', - 'delete_my_account_warning' => 'Biztosan törölni szeretnéd a fiókodat?', + 'delete_my_account' => 'Saját felhasználói fiók törlése', + 'delete_my_account_desc' => 'Ez véglegesen törölni fogja a felhasználói fiókját a rendszerből. Nem lesz lehetősége visszaállítani ezt a fiókot, vagy visszavonni ezt a műveletet. A létrehozott tartalmak, például az oldalak és feltöltött képek megmaradnak.', + 'delete_my_account_warning' => 'Biztosan törölni szeretné a fiókját?', ]; diff --git a/lang/hu/settings.php b/lang/hu/settings.php index aaccbd35f4c..6dd7de33b54 100644 --- a/lang/hu/settings.php +++ b/lang/hu/settings.php @@ -14,46 +14,46 @@ // App Settings 'app_customization' => 'Személyre szabás', - 'app_features_security' => 'Jellemzők és biztonság', + 'app_features_security' => 'Funkciók és biztonság', 'app_name' => 'Alkalmazás neve', 'app_name_desc' => 'Ez a név meg fog jelenni a fejlécben és minden a rendszer által küldött emailben.', 'app_name_header' => 'Név mutatása a fejlécben', 'app_public_access' => 'Nyilvános hozzáférés', - 'app_public_access_desc' => 'Ha engedélyezett, a nem bejelentkezett felhasználók is hozzá tudnak férni a BookStack példány tartalmaihoz.', + 'app_public_access_desc' => 'Az opció engedélyezése lehetővé teszi látogatóknak, hogy bejelentkezés nélkül hozzáférjenek az alkalmazásban tárolt bizonyos tartalmakhoz.', 'app_public_access_desc_guest' => 'A nyilvános látogatók hozzáférése a "Guest" felhasználón keresztül irányítható.', 'app_public_access_toggle' => 'Nyilvános hozzáférés engedélyezése', - 'app_public_viewing' => 'Nyilvános megtekintés engedélyezve?', + 'app_public_viewing' => 'Engedélyezi a nyilvános elérést?', 'app_secure_images' => 'Magasabb biztonságú képfeltöltés', 'app_secure_images_toggle' => 'Magasabb biztonságú képfeltöltés engedélyezése', - 'app_secure_images_desc' => 'Teljesítmény optimalizálási okokból minden kép nyilvános. Ez a beállítás egy véletlenszerű, nehezen kitalálható karakterláncot illeszt a képek útvonalának elejére. Meg kell győződni róla, hogy a könnyű hozzáférés megakadályozása érdekében a könyvtár indexek nincsenek engedélyezve.', + 'app_secure_images_desc' => 'Teljesítmény optimalizálási okokból minden kép nyilvános. Ez a beállítás egy véletlenszerű, nehezen kitalálható karakterláncot illeszt a kép URL-ek elejére. Győződjön meg róla, hogy a könyvtárak indexelése nincsen engedélyezve!', 'app_default_editor' => 'Alapértelmezett oldal szerkesztő', - 'app_default_editor_desc' => 'Válassza ki, hogy alapértelmezés szerint melyik szerkesztőt szeretné használni az új oldalak szerkesztésekor. Ezt felülírhatja oldalszintű szinten, amennyiben az engedélyek lehetővé teszik.', + 'app_default_editor_desc' => 'Válassza ki, hogy alapértelmezés szerint melyik szerkesztőt szeretné használni az új oldalak szerkesztésekor. Ezt felülírhatja az egyes oldalak szintjén, amennyiben az engedélyek lehetővé teszik.', 'app_custom_html' => 'Egyéni HTML fejléc tartalom', 'app_custom_html_desc' => 'Az itt hozzáadott bármilyen tartalom be lesz illesztve minden oldal szekciójának aljára. Ez hasznos a stílusok felülírásához van analitikai kódok hozzáadásához.', - 'app_custom_html_disabled_notice' => 'Az egyéni HTML fejléc tartalom le van tiltva ezen a beállítási oldalon, hogy az esetleg hibásan megadott módosításokat vissza lehessen állítani.', + 'app_custom_html_disabled_notice' => 'Az egyéni HTML fejléc tartalom le van tiltva ezen a beállítási oldalon, hogy az esetleg hibásan megadott módosításokat vissza lehessen vonni.', 'app_logo' => 'Alkalmazás logó', - 'app_logo_desc' => 'Ez az alkalmazás fejléc sávjában van használva többek között. Ennek a képnek 86 képpont magasnak kell lennie. A nagy képek át lesznek méretezve.', + 'app_logo_desc' => 'Ez többek között az alkalmazás fejléc sávjában van használva. A képnek 86 pixel magasnak kell lennie. A nagy képek átméretezésre kerülnek.', 'app_icon' => 'Alkalmazás ikon', - 'app_icon_desc' => 'Ez az ikon a böngésző fülekhez és a gyorsikonokhoz használatos. Ez egy 256 képpont négyzet alakú PNG képnek kell lennie.', + 'app_icon_desc' => 'Ez az ikon a böngésző fülekhez és parancsikonokhoz használatos. Az ikonnak egy 256 pixeles, négyzetes PNG képnek kell lennie.', 'app_homepage' => 'Alkalmazás kezdőlapja', - 'app_homepage_desc' => 'A kezdőlapon az alapértelmezés szerinti nézet helyett megjelenő nézet kiválasztása. A kiválasztott oldalakon figyelmen kívül lesznek hagyva az oldal engedélyek.', + 'app_homepage_desc' => 'Válassza ki a kezdőlapon az alapértelmezett nézet helyett megjelenő oldalt!. A kiválasztott oldalon figyelmen kívül lesznek hagyva a hozzáférési jogosultságok.', 'app_homepage_select' => 'Egy oldal kiválasztása', 'app_footer_links' => 'Lábléc linkek', - 'app_footer_links_desc' => 'Adj hozzá linkeket a weboldal láblécéhez. Ezek a legtöbb oldalon megjelennek, beleértve azokat is, amelyekhez nincs szükség bejelentkezésre. Használhatsz egy "trans::" címkét a rendszer által definiált fordítások használatához. Például: A "trans::common.privacy_policy" használata a lefordított szöveget ("Adatvédelmi Irányelvek") adja vissza, és a "trans::common.terms_of_service" a "Szolgáltatási feltételek" lefordított szöveget adja eredményül.', + 'app_footer_links_desc' => 'Adjon hozzá linkeket a weboldal láblécéhez. Ezek a legtöbb oldalon megjelennek, beleértve azokat is, amelyekhez nincs szükség bejelentkezésre. Használhat egy "trans::" címkét a rendszer által definiált fordítások használatához. Például: A "trans::common.privacy_policy" használata a "Adatvédelmi Irányelvek" lefordított szöveget adja vissza, és a "trans::common.terms_of_service" a "Szolgáltatási feltételek" lefordított szöveget adja eredményül.', 'app_footer_links_label' => 'Link címke', 'app_footer_links_url' => 'Link URL', 'app_footer_links_add' => 'Lábléc hivatkozás hozzáadása', - 'app_disable_comments' => 'Megjegyzések letiltása', - 'app_disable_comments_toggle' => 'Megjegyzések letiltása', - 'app_disable_comments_desc' => 'Megjegyzések letiltása az alkalmazás összes oldalán.
    A már létező megjegyzések el lesznek rejtve.', + 'app_disable_comments' => 'Hozzászólások letiltása', + 'app_disable_comments_toggle' => 'Hozzászólások letiltása', + 'app_disable_comments_desc' => 'Hozzászólások letiltása az alkalmazás összes oldalán.
    A már létező hozzászólások el lesznek rejtve.', // Color settings 'color_scheme' => 'Alkalmazás színséma', - 'color_scheme_desc' => 'Állítsd be a színeket az alkalmazás felhasználói felületén. A színeket külön-külön lehet konfigurálni a sötét és a világos módokhoz, hogy a legjobban illeszkedjenek a témához, és biztosítsák az olvashatóságot.', - 'ui_colors_desc' => 'Állítsa be az alkalmazás elsődleges színét és alapértelmezett hivatkozási színét. Az elsődleges színt főként a fejléc szalaghirdetéséhez, a gombokhoz és a felület díszítéséhez használják. Az alapértelmezett hivatkozásszín a szöveges hivatkozásokhoz és műveletekhez használatos, mind az írott tartalomban, mind az alkalmazás felületén.', + 'color_scheme_desc' => 'Állítsa be az alkalmazás felhasználói felületének színeit. A színeket külön-külön lehet konfigurálni a sötét és a világos módokhoz, hogy a legjobban illeszkedjenek a témához, és biztosítsák az olvashatóságot.', + 'ui_colors_desc' => 'Állítsa be az alkalmazás elsődleges színét és az alapértelmezett hivatkozási színt. Az elsődleges színt főként a fejléc szalag, a gombok és egyéb díszítőelemek használják. Az alapértelmezett hivatkozás szín a szöveges hivatkozásokhoz és műveletekhez használatos, mind az írott tartalomban, mind az alkalmazás felhasználói felületén.', 'app_color' => 'Elsődleges szín', 'link_color' => 'Alapértelmezett link szín', - 'content_colors_desc' => 'Beállítja az elemek színét az oldalszervezési hierarchiában. Az olvashatóság szempontjából javasolt az alapértelmezés szerinti színhez hasonló fényerősséget választani.', + 'content_colors_desc' => 'Állítsa be az elemek színeit az oldal hierarchiában. Az olvashatóság érdekében javasolt az alapértelmezett színekhez hasonló világosságú színeket választani.', 'bookshelf_color' => 'Polc színe', 'book_color' => 'Könyv színe', 'chapter_color' => 'Fejezet színe', @@ -64,65 +64,65 @@ 'reg_settings' => 'Regisztráció', 'reg_enable' => 'Regisztráció engedélyezése', 'reg_enable_toggle' => 'Regisztráció engedélyezése', - 'reg_enable_desc' => 'Ha a regisztráció engedélyezett, akkor a felhasználó képes lesz bejelentkezni mint az alkalmazás egy felhasználója. Regisztráció után egy egyszerű, alapértelmezés szerinti felhasználói szerepkör lesz hozzárendelve.', + 'reg_enable_desc' => 'Ha a regisztráció engedélyezve van, akkor látogatók regisztrálhatják saját magukat az alkalmazásba. Az új felhasználókhoz az alapértelmezett felhasználói szerepkör kerül hozzárendelésre.', 'reg_default_role' => 'Regisztráció utáni alapértelmezett felhasználói szerepkör', - 'reg_enable_external_warning' => 'A fenti beállítási lehetőség nincs használatban, ha külső LDAP vagy SAML hitelesítés aktív. A nem létező tagok felhasználói fiókjai automatikusan létrejönnek ha a használatban lévő külső rendszeren sikeres a hitelesítés.', - 'reg_email_confirmation' => 'Email megerősítés', - 'reg_email_confirmation_toggle' => 'Email megerősítés szükséges', - 'reg_confirm_email_desc' => 'Ha a tartomány korlátozás be van állítva, akkor email megerősítés szükséges és ez a beállítás figyelmen kívül lesz hagyva.', - 'reg_confirm_restrict_domain' => 'Tartomány korlátozás', - 'reg_confirm_restrict_domain_desc' => 'Azoknak az email tartományoknak a vesszővel elválasztott listája, melyekre a regisztráció korlátozva lesz. A felhasználók egy emailt fognak kapni, hogy megerősítsék az email címüket mielőtt használni kezdhetnék az alkalmazást.
    Fontos tudni, hogy a felhasználók a sikeres regisztráció után megváltoztathatják az email címüket.', + 'reg_enable_external_warning' => 'A fenti beállítási lehetőség figyelmen kívül van hagyva, ha külső LDAP vagy SAML hitelesítés aktív. A nem létező tagok felhasználói fiókjai automatikusan létrejönnek ha a használatban lévő külső rendszeren sikeres a hitelesítés.', + 'reg_email_confirmation' => 'Email megerősítése', + 'reg_email_confirmation_toggle' => 'Email megerősítése szükséges', + 'reg_confirm_email_desc' => 'Ha a domain korlátozás be van állítva, akkor az email megerősítése kötelező, és ez a beállítás figyelmen kívül van hagyva.', + 'reg_confirm_restrict_domain' => 'Domain korlátozás', + 'reg_confirm_restrict_domain_desc' => 'Adja meg azon email domain-eket vesszővel tagolt listaként, amikre a regisztrációt korlátozni szeretné. A felhasználók egy emailt fognak kapni, hogy megerősítsék az email címüket mielőtt használni kezdhetnék az alkalmazást.
    Fontos tudni, hogy a felhasználók a sikeres regisztráció után megváltoztathatják az email címüket.', 'reg_confirm_restrict_domain_placeholder' => 'Nincs beállítva korlátozás', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', - 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', - 'sorting_rules' => 'Sort Rules', - 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', - 'sort_rule_assigned_to_x_books' => 'Assigned to :count Book|Assigned to :count Books', - 'sort_rule_create' => 'Create Sort Rule', - 'sort_rule_edit' => 'Edit Sort Rule', - 'sort_rule_delete' => 'Delete Sort Rule', - 'sort_rule_delete_desc' => 'Remove this sort rule from the system. Books using this sort will revert to manual sorting.', - 'sort_rule_delete_warn_books' => 'This sort rule is currently used on :count book(s). Are you sure you want to delete this?', - 'sort_rule_delete_warn_default' => 'This sort rule is currently used as the default for books. Are you sure you want to delete this?', - 'sort_rule_details' => 'Sort Rule Details', - 'sort_rule_details_desc' => 'Set a name for this sort rule, which will appear in lists when users are selecting a sort.', - 'sort_rule_operations' => 'Sort Operations', - 'sort_rule_operations_desc' => 'Configure the sort actions to be performed by moving them from the list of available operations. Upon use, the operations will be applied in order, from top to bottom. Any changes made here will be applied to all assigned books upon save.', - 'sort_rule_available_operations' => 'Available Operations', - 'sort_rule_available_operations_empty' => 'No operations remaining', - 'sort_rule_configured_operations' => 'Configured Operations', - 'sort_rule_configured_operations_empty' => 'Drag/add operations from the "Available Operations" list', - 'sort_rule_op_asc' => '(Asc)', - 'sort_rule_op_desc' => '(Desc)', - 'sort_rule_op_name' => 'Name - Alphabetical', - 'sort_rule_op_name_numeric' => 'Name - Numeric', - 'sort_rule_op_created_date' => 'Created Date', - 'sort_rule_op_updated_date' => 'Updated Date', - 'sort_rule_op_chapters_first' => 'Chapters First', - 'sort_rule_op_chapters_last' => 'Chapters Last', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sorting' => 'Listák és rendezés', + 'sorting_book_default' => 'Alapértelmezett könyv rendezési szabály', + 'sorting_book_default_desc' => 'Válassza ki az új könyvekre alkalmazandó alapértelmezett rendezési szabályt. Ez nem fogja befolyásolni a már létező könyveket, és könyvenként felülbírálható.', + 'sorting_rules' => 'Rendezési szabályok', + 'sorting_rules_desc' => 'Ezek előre meghatározott rendezési szabályok, amelyek alkalmazhatók a rendszerben található tartalomra.', + 'sort_rule_assigned_to_x_books' => 'Hozzárendelve :count könyvhöz|Hozzárendelve :count könyvhöz', + 'sort_rule_create' => 'Rendezési szabály létrehozása', + 'sort_rule_edit' => 'Rendezési szabály szerkesztése', + 'sort_rule_delete' => 'Rendezési szabály törlése', + 'sort_rule_delete_desc' => 'Eltávolítja ezt a rendezési szabályt a rendszerből. A jelenleg ezt használó könyvek vissza lesznek állítva kézi rendezésre.', + 'sort_rule_delete_warn_books' => 'Ez a rendezési szabály :count könyvön van használva. Biztosan törli?', + 'sort_rule_delete_warn_default' => 'Ez a rendezési szabály jelenleg az alapértelmezett az új könyvekhez. Biztosan törli?', + 'sort_rule_details' => 'Rendezési szabály részletei', + 'sort_rule_details_desc' => 'Állítson be egy nevet a rendezési szabálynak, amely megjelenik majd a rendezési listákban.', + 'sort_rule_operations' => 'Rendezési műveletek', + 'sort_rule_operations_desc' => 'Állítsa be rendezési műveleteket az elemek átmozgatásával az elérhető műveletek listájából. A műveletek mindig fentről lefelé haladva kerülnek végrehajtásra. Mentéskor minden itt végzett változtatás az összes hozzárendelt könyvre érvényesítve lesz.', + 'sort_rule_available_operations' => 'Elérhető műveletek', + 'sort_rule_available_operations_empty' => 'Nincs több művelet', + 'sort_rule_configured_operations' => 'Beállított műveletek', + 'sort_rule_configured_operations_empty' => 'Húzzon/adjon hozzá műveleteket az "Elérhető műveletek" listáról', + 'sort_rule_op_asc' => '(Növekvő)', + 'sort_rule_op_desc' => '(Csökkenő)', + 'sort_rule_op_name' => 'Név - betűrend', + 'sort_rule_op_name_numeric' => 'Név - számsorrend', + 'sort_rule_op_created_date' => 'Létrehozás dátuma', + 'sort_rule_op_updated_date' => 'Frissítés dátuma', + 'sort_rule_op_chapters_first' => 'Fejezetek elől', + 'sort_rule_op_chapters_last' => 'Fejezetek hátul', + 'sorting_page_limits' => 'Oldalankénti megjelenítési limitek', + 'sorting_page_limits_desc' => 'Állítsa be oldalanként mennyi elem kerüljön megjelenítésre a rendszer külöböző listáiban. Általában a kisebb mennyiség jobb teljesítményhez vezet, nagyobb mennyiségnél nem kell annyi oldalon végigmenni. Ajánlott a 6 egyik többszörösét használni.', // Maintenance settings 'maint' => 'Karbantartás', - 'maint_image_cleanup' => 'Képek tisztítása', + 'maint_image_cleanup' => 'Képek kitakarítása', 'maint_image_cleanup_desc' => 'Végigolvassa az oldalakat és a tartalmak változatait, hogy leellenőrizze jelenleg mely képek és rajzok vannak használatban, és mely képek szerepelnek többször. A futtatása előtt feltétlen készíteni kell egy teljes adatbázis és lemezkép mentést.', 'maint_delete_images_only_in_revisions' => 'Törölje azokat a képeket is, amelyek csak a régi oldalverziókban léteznek', - 'maint_image_cleanup_run' => 'Tisztítás futtatása', - 'maint_image_cleanup_warning' => ':count potenciálisan nem használt képet találtam. Biztosan törölhetőek ezek a képek?', + 'maint_image_cleanup_run' => 'Takarítás futtatása', + 'maint_image_cleanup_warning' => ':count potenciálisan nem használt kép van. Biztosan törölni szeretné ezeket a képeket?', 'maint_image_cleanup_success' => ':count potenciálisan nem használt kép megtalálva és törölve!', 'maint_image_cleanup_nothing_found' => 'Nincsenek nem használt képek, semmi sem lett törölve!', - 'maint_send_test_email' => 'Teszt e-mail küldése', + 'maint_send_test_email' => 'Teszt email küldése', 'maint_send_test_email_desc' => 'Ez elküld egy teszt emailt a profilban megadott email címre.', - 'maint_send_test_email_run' => 'Teszt e-mail küldése', - 'maint_send_test_email_success' => 'Email elküldve :address címre', - 'maint_send_test_email_mail_subject' => 'Teszt e-mail', + 'maint_send_test_email_run' => 'Teszt email küldése', + 'maint_send_test_email_success' => 'Email elküldve a(z) :address címre', + 'maint_send_test_email_mail_subject' => 'Teszt email', 'maint_send_test_email_mail_greeting' => 'Az email kézbesítés működőképesnek tűnik!', 'maint_send_test_email_mail_text' => 'Gratulálunk! Mivel ez az email figyelmeztetés megérkezett az email beállítások megfelelőek.', - 'maint_recycle_bin_desc' => 'A törölt polcok, könyvek, fejezetek és oldalak a lomtárba kerülnek, így visszaállíthatók vagy véglegesen törölhetők. A rendszer konfigurációtól függően egy idő után a lomtárban lévő régebbi elemek automatikusan eltávolíthatók.', + 'maint_recycle_bin_desc' => 'A törölt polcok, könyvek, fejezetek és oldalak a lomtárba kerülnek, így visszaállíthatók vagy véglegesen törölhetők. A rendszer konfigurációtól függően egy idő után a lomtárban lévő régebbi elemek automatikusan eltávolításra kerülhetnek.', 'maint_recycle_bin_open' => 'Lomtár megnyitása', 'maint_regen_references' => 'Referenciák újragenerálása', 'maint_regen_references_desc' => 'Ez a művelet újraépíti az adatbázison belüli elemek közötti hivatkozási indexet. Ez általában automatikusan történik, de ez a művelet hasznos lehet régi vagy nem hivatalos módszerekkel hozzáadott tartalom indexeléséhez.', @@ -144,7 +144,7 @@ 'recycle_bin_destroy_confirm' => 'Ez a művelet véglegesen törli ezt az elemet a rendszerből az alább felsorolt összes alárendelt elemmel együtt, és nem fogja tudni visszaállítani ezt a tartalmat. Biztosan véglegesen törli ezt az elemet?', 'recycle_bin_destroy_list' => 'Megsemmisítendő elemek', 'recycle_bin_restore_list' => 'Visszaállítandó elemek', - 'recycle_bin_restore_confirm' => 'Ez a művelet visszaállítja a törölt elemet, beleértve az utódelemeket is, az eredeti helyükre. Ha az eredeti helyet azóta törölték, és most a lomtárban van, akkor a szülőelemet is vissza kell állítani.', + 'recycle_bin_restore_confirm' => 'Ez a művelet visszaállítja a törölt elemet, beleértve az alárendelt elemeket is, az eredeti helyükre. Ha az eredeti helyet azóta törölték, és most a lomtárban van, akkor a tartalmazó elemet is vissza kell állítani.', 'recycle_bin_restore_deleted_parent' => 'Ennek az elemnek a szülője is törölve lett. Ezek mindaddig törölve maradnak, amíg az adott szülőt is vissza nem állítják.', 'recycle_bin_restore_parent' => 'Szűlő visszaállítása', 'recycle_bin_destroy_notification' => 'Összesen :count elemet törölt a lomtárból.', @@ -160,7 +160,7 @@ 'audit_table_user' => 'Felhasználó', 'audit_table_event' => 'Esemény', 'audit_table_related' => 'Kapcsolódó elem vagy részlet', - 'audit_table_ip' => 'IP Cím', + 'audit_table_ip' => 'IP cím', 'audit_table_date' => 'Tevékenység időpontja', 'audit_date_from' => 'Kezdő dátum', 'audit_date_to' => 'Végdátum', @@ -168,46 +168,46 @@ // Role Settings 'roles' => 'Szerepkörök', 'role_user_roles' => 'Felhasználói szerepkörök', - 'roles_index_desc' => 'A szerepkörök a felhasználók csoportosítására és rendszerengedélyek biztosítására szolgálnak tagjaiknak. Ha egy felhasználó több szerepkör tagja, a megadott jogosultságok halmozódnak, és a felhasználó örökli az összes képességet.', + 'roles_index_desc' => 'A szerepkörök a felhasználók csoportosítására és rendszerengedélyek biztosítására szolgálnak. Ha egy felhasználó több szerepkör tagja, a megadott jogosultságok halmozódnak, és a felhasználó örökli az összes képességet.', 'roles_x_users_assigned' => ':count hozzárendelt felhasználó|:count hozzárendelt felhasználó', 'roles_x_permissions_provided' => ':count jogosultság|:count jogosultság', 'roles_assigned_users' => 'Hozzárendelt felhasználók', 'roles_permissions_provided' => 'Megadott jogosultságok', 'role_create' => 'Új szerepkör létrehozása', 'role_delete' => 'Szerepkör törlése', - 'role_delete_confirm' => 'Ez törölni fogja \':roleName\' szerepkört.', - 'role_delete_users_assigned' => 'Ehhez a szerepkörhöz :userCount felhasználó van hozzárendelve. Ha a felhasználókat át kell helyezni ebből a szerepkörből, akkor ki kell választani egy új szerepkört.', - 'role_delete_no_migration' => "Nincs felhasználó áthelyezés", - 'role_delete_sure' => 'Biztosan törölhető ez a szerepkör?', + 'role_delete_confirm' => 'Ez törölni fogja a(z) \':roleName\' szerepkört.', + 'role_delete_users_assigned' => 'Ehhez a szerepkörhöz :userCount felhasználó van hozzárendelve. Ha át szeretné helyezni a felhasználókat egy másik szerepkörbe, akkor válasszon egy új szerepkört az alábbi listából.', + 'role_delete_no_migration' => "Ne helyezze át a felhasználókat", + 'role_delete_sure' => 'Biztosan törli ezt a szerepkört?', 'role_edit' => 'Szerepkör szerkesztése', 'role_details' => 'Szerepkör részletei', 'role_name' => 'Szerepkör neve', 'role_desc' => 'Szerepkör rövid leírása', - 'role_mfa_enforced' => 'Kétlépcsős hitelesítés megkövetelése', + 'role_mfa_enforced' => 'Többlépcsős hitelesítés megkövetelése', 'role_external_auth_id' => 'Külső hitelesítés azonosítók', 'role_system' => 'Rendszer jogosultságok', 'role_manage_users' => 'Felhasználók kezelése', - 'role_manage_roles' => 'Szerepkörök és szerepkör engedélyek kezelése', + 'role_manage_roles' => 'Szerepkörök és jogosultságok kezelése', 'role_manage_entity_permissions' => 'Minden könyv, fejezet és oldalengedély kezelése', 'role_manage_own_entity_permissions' => 'Saját könyv, fejezet és oldalak engedélyeinek kezelése', 'role_manage_page_templates' => 'Oldalsablonok kezelése', 'role_access_api' => 'Hozzáférés a rendszer API-hoz', 'role_manage_settings' => 'Alkalmazás beállításainak kezelése', 'role_export_content' => 'Tartalom exportálása', - 'role_import_content' => 'Import content', + 'role_import_content' => 'Tartalom importálása', 'role_editor_change' => 'Oldalszerkesztő módosítása', 'role_notifications' => 'Értesítések fogadása és kezelése', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', - 'role_asset' => 'Eszköz jogosultságok', + 'role_permission_note_users_and_roles' => 'Ezek a jogosultságok gyakorlatilag megtekintést és felhasználó/szerepkör keresést is lehetővé fognak tenni.', + 'role_asset' => 'Tartalom jogosultságok', 'roles_system_warning' => 'Ne feledje, hogy a fenti három engedély bármelyikéhez való hozzáférés lehetővé teszi a felhasználó számára, hogy módosítsa saját vagy a rendszerben mások jogosultságait. Csak megbízható felhasználókhoz rendeljen szerepeket ezekkel az engedélyekkel.', - 'role_asset_desc' => 'Ezek a jogosultságok vezérlik az alapértelmezés szerinti hozzáférést a rendszerben található eszközökhöz. A könyvek, fejezetek és oldalak jogosultságai felülírják ezeket a jogosultságokat.', - 'role_asset_admins' => 'Az adminisztrátorok automatikusan hozzáférést kapnak minden tartalomhoz, de ezek a beállítások megjeleníthetnek vagy elrejthetnek felhasználói felület beállításokat.', + 'role_asset_desc' => 'Ezek a jogosultságok vezérlik az alapértelmezés szerinti hozzáférést a rendszerben található tartalomhoz. A könyvek, fejezetek és oldalak jogosultságai felülírják ezeket a jogosultságokat.', + 'role_asset_admins' => 'Az adminisztrátorok automatikusan hozzáférést kapnak minden tartalomhoz, de ezek a beállítások megjeleníthetnek vagy elrejthetnek beállításokat a felhasználói felületen.', 'role_asset_image_view_note' => 'Ez a képkezelőn belüli láthatóságra vonatkozik. A feltöltött képfájlok tényleges elérése a rendszerkép tárolási beállításától függ.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Ezek a jogosultságok gyakorlatilag megtekintést és felhasználó keresést is lehetővé fognak tenni.', 'role_all' => 'Összes', 'role_own' => 'Saját', - 'role_controlled_by_asset' => 'Az általuk feltöltött eszköz által ellenőrzött', - 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', + 'role_controlled_by_asset' => 'A feltöltött tartalom beállításaitól függ', + 'role_controlled_by_page_delete' => 'Az oldal törlési jogosultságoktól függ', 'role_save' => 'Szerepkör mentése', 'role_users' => 'Felhasználók ebben a szerepkörben', 'role_users_none' => 'Jelenleg nincsenek felhasználók hozzárendelve ehhez a szerepkörhöz', @@ -220,72 +220,75 @@ 'users_search' => 'Felhasználók keresése', 'users_latest_activity' => 'Legújabb tevékenység', 'users_details' => 'Felhasználó részletei', - 'users_details_desc' => 'Egy megjelenítendő név és email cím beállítása ennek a felhasználónak. Az email cím az alkalmazásba történő bejelentkezéshez lesz használva.', - 'users_details_desc_no_email' => 'Egy megjelenítendő név beállítása ennek a felhasználónak amiről mások felismerik.', + 'users_details_desc' => 'Megjelenítendő név és email cím beállítása ennek a felhasználónak. Az email cím az alkalmazásba történő bejelentkezéshez lesz használva.', + 'users_details_desc_no_email' => 'Megjelenítendő név beállítása ennek a felhasználónak, amiről mások felismerik.', 'users_role' => 'Felhasználói szerepkörök', - 'users_role_desc' => 'A felhasználó melyik szerepkörhöz lesz rendelve. Ha a felhasználó több szerepkörhöz van rendelve, akkor ezeknek a szerepköröknek a jogosultságai összeadódnak, és a a felhasználó a hozzárendelt szerepkörök minden képességét megkapja.', + 'users_role_desc' => 'Felhasználó szerepköreinek kiválasztása. Ha a felhasználó több szerepkörhöz van rendelve, akkor ezeknek a szerepköröknek a jogosultságai összeadódnak, és a a felhasználó a hozzárendelt szerepkörök minden képességét megkapja.', 'users_password' => 'Felhasználó jelszava', 'users_password_desc' => 'Az alkalmazásba bejelentkezéshez használható jelszó beállítása. Legalább 8 karakter hosszúnak kell lennie.', - 'users_send_invite_text' => 'Lehetséges egy meghívó emailt küldeni ennek a felhasználónak ami lehetővé teszi, hogy beállíthassa a saját jelszavát. Máskülönben a jelszót az erre jogosult felhasználónak kell beállítania.', + 'users_send_invite_text' => 'Dönthet úgy, hogy meghívó emailt küld ennek a felhasználónak, így az beállíthatja saját jelszavát. Ellenkező esetben Ön is beállíthat egy jelszót neki.', 'users_send_invite_option' => 'Felhasználó meghívó levél küldése', 'users_external_auth_id' => 'Külső hitelesítés azonosítója', - 'users_external_auth_id_desc' => 'Ha külső hitelesítési rendszer van használatban (például SAML2, OIDC vagy LDAP), ez az az azonosító, amely a BookStack felhasználót a hitelesítési rendszerfiókhoz kapcsolja. Ha az alapértelmezett e-mail alapú hitelesítést használja, figyelmen kívül hagyhatja ezt a mezőt.', + 'users_external_auth_id_desc' => 'Ha külső hitelesítési rendszer van használatban (például SAML2, OIDC vagy LDAP), ez az az azonosító, amely a BookStack felhasználót a hitelesítési rendszerfiókhoz kapcsolja. Ha az alapértelmezett email alapú hitelesítést használja, figyelmen kívül hagyhatja ezt a mezőt.', 'users_password_warning' => 'Csak akkor töltse ki az alábbi mezőt, ha módosítani szeretné ennek a felhasználónak a jelszavát.', - 'users_system_public' => 'Ez a felhasználó bármelyik, a példányt megtekintő felhasználót képviseli. Nem lehet vele bejelentkezni de automatikusan hozzá lesz rendelve.', + 'users_system_public' => 'Ez a felhasználó bármelyik, az alkalmazást megtekintő látogatót képviseli. Nem lehet vele bejelentkezni de automatikusan hozzá lesz rendelve.', 'users_delete' => 'Felhasználó törlése', 'users_delete_named' => ':userName felhasználó törlése', 'users_delete_warning' => '\':userName\' felhasználó teljesen törölve lesz a rendszerből.', - 'users_delete_confirm' => 'Biztosan törölhető ez a felhasználó?', + 'users_delete_confirm' => 'Biztosan törli ezt a felhasználót?', 'users_migrate_ownership' => 'Tulajdonjog átruházása', 'users_migrate_ownership_desc' => 'Válasszon itt egy felhasználót, ha azt szeretné, hogy egy másik felhasználó legyen a tulajdonosa az összes, jelenleg a felhasználó tulajdonában lévő elemnek.', 'users_none_selected' => 'Nincs felhasználó kiválasztva', 'users_edit' => 'Felhasználó szerkesztése', 'users_edit_profile' => 'Profil szerkesztése', - 'users_avatar' => 'Avatar használata', - 'users_avatar_desc' => 'A felhasználót ábrázoló kép kiválasztása. Kb. 256px méretű négyzetes képnek kell lennie.', + 'users_avatar' => 'Felhasználó profilképe', + 'users_avatar_desc' => 'Válasszon ki egy képet, ami ehhez a felhasználóhoz fog tartozni. Négyzetes, körülbelül 256 pixel széles képnek kell lennie.', 'users_preferred_language' => 'Előnyben részesített nyelv', 'users_preferred_language_desc' => 'Ez a beállítás megváltoztatja az alkalmazás felhasználói felületén használt nyelvet. Nincs hatása a felhasználók által létrehozott tartalomra.', - 'users_social_accounts' => 'Közösségi fiókok', + 'users_social_accounts' => 'Közösségi média fiókok', 'users_social_accounts_desc' => 'Tekintse meg a felhasználó csatlakoztatott közösségi fiókjainak állapotát. A közösségi fiókok az elsődleges hitelesítési rendszer mellett használhatók a rendszerhez való hozzáféréshez.', - 'users_social_accounts_info' => 'Itt lehet egyéb fiókokat hozzákapcsolni a gyorsabb és könnyebb bejelentkezés érdekében. Itt olyan fiókot lehet lecsatlakoztatni, melynek korábban nem volt engedélyezett hozzáférése. Visszavonja a hozzáférést a csatlakoztatott szociális fiók profilbeállításaiból.', + 'users_social_accounts_info' => 'Itt kapcsolhat össze külső fiókokat a gyorsabb és egyszerűbb bejelentkezés érdekében. Egy összekapcsolás megszüntetése itt, nem vonja vissza a már korábban engedélyezett hozzáférést. Vonja vissza a hozzáférést a külső fiók profil beállításaiban.', 'users_social_connect' => 'Fiók csatlakoztatása', 'users_social_disconnect' => 'Fiók lecsatlakoztatása', 'users_social_status_connected' => 'Csatlakozva', 'users_social_status_disconnected' => 'Lecsatlakozva', 'users_social_connected' => ':socialAccount fiók sikeresen csatlakoztatva a profilhoz.', 'users_social_disconnected' => ':socialAccount fiók sikeresen lecsatlakoztatva a profilról.', - 'users_api_tokens' => 'API vezérjelek', - 'users_api_tokens_desc' => 'A BookStack REST API-val történő hitelesítéshez használt hozzáférési token létrehozása és kezelése. Az API engedélyeit azon a felhasználón keresztül kezelik, akihez a token tartozik.', - 'users_api_tokens_none' => 'Ehhez a felhasználóhoz nincsenek létrehozva API vezérjelek', - 'users_api_tokens_create' => 'Vezérjel létrehozása', + 'users_api_tokens' => 'API kulcsok', + 'users_api_tokens_desc' => 'A BookStack REST API-val történő hitelesítéshez használt hozzáférési kulcs létrehozása és kezelése. Az API engedélyeit azon a felhasználón keresztül kezelik, akihez a token tartozik.', + 'users_api_tokens_none' => 'Ehhez a felhasználóhoz nincsenek létrehozva API kulcsok', + 'users_api_tokens_create' => 'Kulcs létrehozása', 'users_api_tokens_expires' => 'Lejárat', 'users_api_tokens_docs' => 'API dokumentáció', - 'users_mfa' => 'Többfaktoros hitelesítés', + 'users_mfa' => 'Többlépcsős hitelesítés', 'users_mfa_desc' => 'Állítsa be a többlépcsős azonosítást egy extra biztonsági rétegként a felhasználói fiókjához.', - 'users_mfa_x_methods' => ':count metódus konfigurálva|:count metódus konfigurálva', + 'users_mfa_x_methods' => ':count mód beállítva|:count mód beállítva', 'users_mfa_configure' => 'Módszer beállítása', + 'users_mfa_reset' => 'Többlépcsős azonosítási módok alaphelyzetbe állítása', + 'users_mfa_reset_desc' => 'Ez alaphelyzetbe fogja állítani a felhasználónak az összes beállított többlépcsős azonosítási módját. Ha valamelyik a szerepkörük előírja a többlépcsős azonosítást, akkor a következő bejelentkezésnél fel lesznek szólítva egy új beállítására.', + 'users_mfa_reset_confirm' => 'Biztos benne, hogy alaphelyzetbe szeretné állítani ennek a felhasználónak a többlépcsős azonosítási módjait?', // API Tokens - 'user_api_token_create' => 'API vezérjel létrehozása', + 'user_api_token_create' => 'API kulcs létrehozása', 'user_api_token_name' => 'Név', - 'user_api_token_name_desc' => 'Adjon a tokennek egy olvasható nevet, hogy a jövőben emlékeztessen a tervezett céljára.', + 'user_api_token_name_desc' => 'Adjon a kulcsnak egy olvasható nevet, hogy a jövőben emlékeztessen a tervezett céljára.', 'user_api_token_expiry' => 'Lejárati dátum', - 'user_api_token_expiry_desc' => 'Dátum megadása ameddig a vezérjel érvényes. Ez után a dátum után az ezzel a vezérjellel történő kérések nem fognak működni. Üresen hagyva a lejárati idő 100 évre lesz beállítva.', - 'user_api_token_create_secret_message' => 'Közvetlenül a token létrehozása után egy „Token ID” és „Token Secret” generálódik és jelenik meg. A Secret csak egyszer jelenik meg, ezért a folytatás előtt másolja át az értéket egy biztonságos helyre.', - 'user_api_token' => 'API vezérjel', - 'user_api_token_id' => 'Vezérjel azonosító', - 'user_api_token_id_desc' => 'Ez egy nem szerkeszthető, a rendszer által létrehozott azonosító ehhez a vezérjelhez amire API kérésekben lehet szükség.', - 'user_api_token_secret' => 'Vezérjel titkos kódja', - 'user_api_token_secret_desc' => 'Ez egy rendszer által generált "secret" ehhez a tokenhez, amelyet meg kell adni az API-kérésekben. Ez csak most jelenik meg, ezért másolja ezt az értéket egy biztonságos helyre.', - 'user_api_token_created' => 'Vezérjel létrehozva :timeAgo', - 'user_api_token_updated' => 'Vezérjel frissítve :timeAgo', - 'user_api_token_delete' => 'Vezérjel törlése', - 'user_api_token_delete_warning' => '\':tokenName\' nevű API vezérjel teljesen törölve lesz a rendszerből.', - 'user_api_token_delete_confirm' => 'Biztosan törölhető ez az API vezérjel?', + 'user_api_token_expiry_desc' => 'Dátum ameddig a kulcs érvényes. Ez után a dátum után az ezzel a kulccsal történő kérések nem fognak működni. Üresen hagyva a lejárati idő 100 évre lesz beállítva.', + 'user_api_token_create_secret_message' => 'Közvetlenül a kulcs létrehozása után egy „Kulcs azonosító” és „Titkos kód” generálódik és jelenik meg. A titkos kód csak egyszer jelenik meg, ezért a folytatás előtt másolja át az értéket egy biztonságos helyre.', + 'user_api_token' => 'API kulcs', + 'user_api_token_id' => 'Kulcs azonosító', + 'user_api_token_id_desc' => 'Ez egy nem szerkeszthető, a rendszer által létrehozott azonosító ehhez a kulcshoz, amire API kérésekben lehet szükség.', + 'user_api_token_secret' => 'Kulcs titkos kódja', + 'user_api_token_secret_desc' => 'Ez egy rendszer által generált "titok" ehhez a kulcshoz, amelyet meg kell adni az API-kérésekben. Ez csak most jelenik meg, ezért másolja ezt az értéket egy biztonságos helyre.', + 'user_api_token_created' => 'Kulcs létrehozva :timeAgo', + 'user_api_token_updated' => 'Kulcs frissítve :timeAgo', + 'user_api_token_delete' => 'Kulcs törlése', + 'user_api_token_delete_warning' => '\':tokenName\' nevű API kulcs teljesen törölve lesz a rendszerből.', + 'user_api_token_delete_confirm' => 'Biztosan törli ezt az API kulcsot?', // Webhooks 'webhooks' => 'Webhook-ok', - 'webhooks_index_desc' => 'A webhookok segítségével adatokat küldhetünk külső URL-ekre, amikor bizonyos műveletek és események történnek a rendszeren belül, ami lehetővé teszi az eseményalapú integrációt külső platformokkal, például üzenetküldő vagy értesítési rendszerekkel.', + 'webhooks_index_desc' => 'A webhookok segítségével adatokat küldhet külső URL-ekre, amikor bizonyos műveletek és események történnek a rendszeren belül. Ez lehetővé teszi az eseményalapú integrációt külső platformokkal, például üzenetküldő vagy értesítési rendszerekkel.', 'webhooks_x_trigger_events' => ':count kiváltó esemény|:count kiváltó esemény', 'webhooks_create' => 'Új webhook létrehozása', 'webhooks_none_created' => 'Még nincs létrehozva egy webhook sem.', @@ -303,19 +306,19 @@ 'webhooks_active' => 'Webhook aktív', 'webhook_events_table_header' => 'Események', 'webhooks_delete' => 'Webhook törlése', - 'webhooks_delete_warning' => 'Ezzel a \':webhookName\' nevű webhookot teljesen törli a rendszerből.', + 'webhooks_delete_warning' => 'Ezzel a(z) \':webhookName\' nevű webhookot teljesen törli a rendszerből.', 'webhooks_delete_confirm' => 'Biztosan törli ezt a webhookot?', 'webhooks_format_example' => 'Webhook formátum példa', - 'webhooks_format_example_desc' => 'A Webhook-adatok POST-kérésként kerülnek elküldésre a konfigurált végponthoz JSON-ként az alábbi formátumban. A "related_item" és az "url" tulajdonság nem kötelező, és az aktivált esemény típusától függ.', + 'webhooks_format_example_desc' => 'A Webhook-adatok POST-kérésként kerülnek elküldésre a beállított végponthoz JSON-ként az alábbi formátumban. A "related_item" és az "url" tulajdonság nem kötelező, és az aktivált esemény típusától függ.', 'webhooks_status' => 'Webhook állapota', 'webhooks_last_called' => 'Utolsó hívás:', 'webhooks_last_errored' => 'Utolsó hiba:', 'webhooks_last_error_message' => 'Utolsó hibaüzenet:', // Licensing - 'licenses' => 'Licenszek', + 'licenses' => 'Licencek', 'licenses_desc' => 'Ez az oldal a BookStack licencinformációit részletezi, a BookStackben használt projekteken és könyvtárakon kívül. Sok felsorolt projekt csak fejlesztési környezetben használható.', - 'licenses_bookstack' => 'BookStack Licensz', + 'licenses_bookstack' => 'BookStack licenc', 'licenses_php' => 'PHP könyvtár licencek', 'licenses_js' => 'JavaScript könyvtár licencek', 'licenses_other' => 'Egyéb licencek', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/hu/validation.php b/lang/hu/validation.php index b740ca0c60a..ec6f68fe215 100644 --- a/lang/hu/validation.php +++ b/lang/hu/validation.php @@ -8,56 +8,56 @@ return [ // Standard laravel validation lines - 'accepted' => ':attribute elfogadott kell legyen.', - 'active_url' => ':attribute nem érvényes webcím.', - 'after' => ':attribute dátumnak :date utáninak kell lennie.', - 'alpha' => ':attribute csak betűket tartalmazhat.', - 'alpha_dash' => ':attribute csak betűket, számokat és kötőjeleket tartalmazhat.', - 'alpha_num' => ':attribute csak betűket és számokat tartalmazhat.', - 'array' => ':attribute tömb kell legyen.', + 'accepted' => 'A(z) :attribute elfogadott kell legyen.', + 'active_url' => 'A(z) :attribute nem egy érvényes URL.', + 'after' => 'A(z) :attribute objektumnak egy :date utáni dátumnak kell lennie.', + 'alpha' => 'A(z) :attribute csak betűket tartalmazhat.', + 'alpha_dash' => 'A(z) :attribute csak betűket, számokat, kötőjeleket és alávonásokat tartalmazhat.', + 'alpha_num' => 'A(z) :attribute csak betűket és számokat tartalmazhat.', + 'array' => 'A(z) :attribute tömb kell legyen.', 'backup_codes' => 'A megadott kód érvénytelen, vagy már felhasználták.', - 'before' => ':attribute dátumnak :date előttinek kell lennie.', + 'before' => 'A(z) :attribute objektumnak egy :date előtti dátumnak kell lennie.', 'between' => [ - 'numeric' => ':attribute értékének :min és :max között kell lennie.', - 'file' => ':attribute értékének :min és :max kilobájt között kell lennie.', - 'string' => ':attribute hosszának :min és :max karakter között kell lennie.', - 'array' => ':attribute mennyiségének :min és :max elem között kell lennie.', + 'numeric' => 'A(z) :attribute értékének :min és :max között kell lennie.', + 'file' => 'A(z) :attribute értékének :min és :max kB között kell lennie.', + 'string' => 'A(z) :attribute hosszának :min és :max karakter között kell lennie.', + 'array' => 'A(z) :attribute tömbnek :min és :max közötti elemszámának kell lennie.', ], - 'boolean' => ':attribute mezőnek igaznak vagy hamisnak kell lennie.', - 'confirmed' => ':attribute megerősítés nem egyezik.', - 'date' => ':attribute nem érvényes dátum.', - 'date_format' => ':attribute nem egyezik :format formátummal.', - 'different' => ':attribute és :other értékének különböznie kell.', - 'digits' => ':attribute :digits számból kell álljon.', - 'digits_between' => ':attribute hosszának :min és :max számjegy között kell lennie.', - 'email' => ':attribute érvényes email cím kell legyen.', - 'ends_with' => ':attribute attribútumnak a következők egyikével kell végződnie: :values', - 'file' => 'A(z) :attribute érvényes fájlnak kell lennie.', - 'filled' => ':attribute mező kötelező.', + 'boolean' => 'A(z) :attribute mezőnek igaznak vagy hamisnak kell lennie.', + 'confirmed' => 'A(z) :attribute megerősítés nem egyezik.', + 'date' => 'A(z) :attribute nem egy érvényes dátum.', + 'date_format' => 'A(z) :attribute nem egyezik a(z) :format formátummal.', + 'different' => 'A(z) :attribute és :other értékének különböznie kell.', + 'digits' => 'A(z) :attribute :digits számjegyből kell álljon.', + 'digits_between' => 'A(z) :attribute hosszának :min és :max számjegy között kell lennie.', + 'email' => 'A(z) :attribute érvényes email cím kell legyen.', + 'ends_with' => 'A(z) :attribute értékének a következők egyikével kell végződnie: :values', + 'file' => 'A(z) :attribute érvényes fájl kell legyen.', + 'filled' => 'A(z) :attribute mező kötelező.', 'gt' => [ - 'numeric' => ':attribute nagyobb kell, hogy legyen, mint :value.', - 'file' => ':attribute nagyobb kell, hogy legyen, mint :value kilobájt.', - 'string' => ':attribute nagyobb kell legyen mint :value karakter.', - 'array' => ':attribute több, mint :value elemet kell, hogy tartalmazzon.', + 'numeric' => 'A(z) :attribute nagyobb kell, hogy legyen, mint :value.', + 'file' => 'A(z) :attribute nagyobb kell, hogy legyen, mint :value kB.', + 'string' => 'A(z) :attribute hosszabb kell legyen mint :value karakter.', + 'array' => 'A(z) :attribute több, mint :value elemet kell, hogy tartalmazzon.', ], 'gte' => [ - 'numeric' => ':attribute attribútumnak :value értéknél nagyobbnak vagy vele egyenlőnek kell lennie.', - 'file' => 'A(z) :attribute mérete nem lehet kevesebb, mint :value kilobájt.', - 'string' => 'A(z) :attribute nagyobbnak, vagy egyenlőnek kell lennie, mint a :value karakter.', - 'array' => 'A(z) :attribute rendelkezzen :value vagy több elemmel.', + 'numeric' => 'A(z) :attribute számnak :value értéknél nagyobbnak vagy vele egyenlőnek kell lennie.', + 'file' => 'A(z) :attribute mérete nem lehet kevesebb, mint :value kB.', + 'string' => 'A(z) :attribute szövegnek legalább :value karakter hosszúnak kell lennie.', + 'array' => 'A(z) :attribute tömbnek :value vagy több elemmel kell rendelkeznie.', ], 'exists' => 'A kiválasztott :attribute érvénytelen.', - 'image' => ':attribute kép kell legyen.', - 'image_extension' => 'A :attribute kép kiterjesztése érvényes és támogatott kell legyen.', + 'image' => 'A(z) :attribute kép kell legyen.', + 'image_extension' => 'A(z) :attribute kép kiterjesztése érvényes és támogatott kell legyen.', 'in' => 'A kiválasztott :attribute érvénytelen.', - 'integer' => ':attribute egész szám kell legyen.', - 'ip' => ':attribute érvényes IP cím kell legyen.', - 'ipv4' => 'A(z) :attribute érvényes IPv4 címnek kell lennie.', - 'ipv6' => 'A(z) :attribute érvényes IPv6 címnek kell lennie.', - 'json' => 'A(z) :attribute érvényes JSON stringnek kell lennie.', + 'integer' => 'A(z) :attribute egész szám kell legyen.', + 'ip' => 'A(z) :attribute érvényes IP cím kell legyen.', + 'ipv4' => 'A(z) :attribute érvényes IPv4 cím kell legyen.', + 'ipv6' => 'A(z) :attribute érvényes IPv6 cím kell legyen.', + 'json' => 'A(z) :attribute érvényes JSON szöveg kell legyen.', 'lt' => [ - 'numeric' => 'A(z) :attribute kisebb kell, hogy legyen, mint :value.', - 'file' => 'A(z) :attribute kevesebbnek kell lennie, mint :value kilobájt.', + 'numeric' => 'A(z) :attribute szám kisebb kell, hogy legyen, mint :value.', + 'file' => 'A(z) :attribute fájlnak kisebbnek kell lennie, mint :value kB.', 'string' => 'A(z) :attribute rövidebb kell, hogy legyen, mint :value karakter.', 'array' => 'A(z) :attribute kevesebb, mint :value elemet kell, hogy tartalmazzon.', ], @@ -69,47 +69,47 @@ ], 'max' => [ 'numeric' => ':attribute nem lehet nagyobb mint :max.', - 'file' => ':attribute nem lehet nagyobb mint :max kilobájt.', - 'string' => ':attribute nem lehet nagyobb mint :max karakter.', - 'array' => ':attribute mennyisége nem lehet több mint :max elem.', + 'file' => 'A(z) :attribute nem lehet nagyobb, mint :max kB.', + 'string' => 'A(z) :attribute nem lehet hosszabb, mint :max karakter.', + 'array' => 'A(z) :attribute nem tartalmazhat több, mint :max elemet.', ], - 'mimes' => 'A :attribute típusa csak :values lehet.', + 'mimes' => 'A(z) :attribute típusa csak :values lehet.', 'min' => [ - 'numeric' => ':attribute legalább :min kell legyen.', - 'file' => ':attribute legalább :min kilobájt kell legyen.', - 'string' => ':attribute legalább :min karakter kell legyen.', - 'array' => ':attribute legalább :min elem kell legyen.', + 'numeric' => 'A(z) :attribute legalább :min kell legyen.', + 'file' => 'A(z) :attribute legalább :min kB kell legyen.', + 'string' => 'A(z) :attribute legalább :min karakter kell legyen.', + 'array' => 'A(z) :attribute legalább :min elemet kell tartalmazzon.', ], 'not_in' => 'A kiválasztott :attribute érvénytelen.', - 'not_regex' => ':attribute formátuma érvénytelen.', - 'numeric' => ':attribute szám kell legyen.', - 'regex' => ':attribute formátuma érvénytelen.', - 'required' => ':attribute mező kötelező.', - 'required_if' => ':attribute mező kötelező ha :other értéke :value.', - 'required_with' => ':attribute mező kötelező ha :values be van állítva.', - 'required_with_all' => ':attribute mező kötelező ha van :value.', - 'required_without' => ':attribute mező kötelező ha :values nincs beállítva.', - 'required_without_all' => ':attribute mező kötelező ha egyik :values sincs beállítva.', - 'same' => ':attribute és :other értékének egyeznie kell.', + 'not_regex' => 'A(z) :attribute formátuma érvénytelen.', + 'numeric' => 'A(z) :attribute szám kell legyen.', + 'regex' => 'A(z) :attribute formátuma érvénytelen.', + 'required' => 'A(z) :attribute mező kötelező.', + 'required_if' => 'A(z) :attribute mező kötelező ha :other értéke :value.', + 'required_with' => 'A(z) :attribute mező kötelező ha :values be van állítva.', + 'required_with_all' => 'A(z) :attribute mező kötelező ha :values be van állítva.', + 'required_without' => 'A(z) :attribute mező kötelező ha :values nincs beállítva.', + 'required_without_all' => 'A(z) :attribute mező kötelező ha egyik :values sincs beállítva.', + 'same' => 'A(z) :attribute és :other értékének egyeznie kell.', 'safe_url' => 'Előfordulhat, hogy a megadott link nem biztonságos.', 'size' => [ - 'numeric' => ':attribute :size méretű kell legyen.', - 'file' => ':attribute :size kilobájt méretű kell legyen.', - 'string' => ':attribute :size karakter kell legyen.', - 'array' => ':attribute : size elemet kell tartalmazzon.', + 'numeric' => 'A(z) :attribute :size méretű kell legyen.', + 'file' => 'A(z) :attribute :size kB méretű kell legyen.', + 'string' => 'A(z) :attribute :size karakter kell legyen.', + 'array' => 'A(z) :attribute :size elemet kell tartalmazzon.', ], - 'string' => ':attribute karaktersorozatnak kell legyen.', - 'timezone' => ':attribute érvényes zóna kell legyen.', + 'string' => 'A(z) :attribute szöveg kell legyen.', + 'timezone' => 'A(z) :attribute érvényes időzóna kell legyen.', 'totp' => 'A megadott kód érvénytelen vagy lejárt.', - 'unique' => ':attribute már elkészült.', - 'url' => ':attribute formátuma érvénytelen.', - 'uploaded' => 'A fájlt nem lehet feltölteni. A kiszolgáló nem fogad el ilyen méretű fájlokat.', + 'unique' => 'A(z) :attribute már foglalt.', + 'url' => 'A(z) :attribute formátuma érvénytelen.', + 'uploaded' => 'A fájlt nem lehet feltölteni. A szerver nem fogad el ilyen méretű fájlokat.', - 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', - 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', - 'zip_model_expected' => 'Data object expected but ":type" found.', - 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', + 'zip_file' => 'A(z) :attribute egy a ZIP fájlban található fájlra kell, hogy hivatkozzon', + 'zip_file_size' => 'A(z) :attribute fájl nem haladhatja meg a :size MB méretet.', + 'zip_file_mime' => 'A(z) :attribute egy :validTypes típusú fájlra kell, hogy hivatkozzon, a :foundType helyett.', + 'zip_model_expected' => 'Adat objektum helyett ":type" lett találva.', + 'zip_unique' => 'A(z) :attribute egyedi kell hogy legyen a ZIP fájlban az adott objektum típushoz.', // Custom validation lines 'custom' => [ diff --git a/lang/id/activities.php b/lang/id/activities.php index 6e1583a94e7..db32fcf644a 100644 --- a/lang/id/activities.php +++ b/lang/id/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Pengguna berhasil diperbarui', 'user_delete' => 'pengguna yang dihapus', 'user_delete_notification' => 'Pengguna berhasil dihapus', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'API token yang dibuat', diff --git a/lang/id/auth.php b/lang/id/auth.php index 6af60ca80b1..a5653ede685 100644 --- a/lang/id/auth.php +++ b/lang/id/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Kredensial tidak cocok dengan catatan kami.', 'throttle' => 'Terlalu banyak upaya masuk. Silahkan mencoba lagi dalam :seconds detik.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Daftar', diff --git a/lang/id/entities.php b/lang/id/entities.php index d978a3d19b1..a423ef39c88 100644 --- a/lang/id/entities.php +++ b/lang/id/entities.php @@ -331,6 +331,9 @@ // 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' => 'Halaman Tag', 'chapter_tags' => 'Bab Tag', 'book_tags' => 'Tag Buku', diff --git a/lang/id/settings.php b/lang/id/settings.php index fe3289ffa46..5d785314701 100644 --- a/lang/id/settings.php +++ b/lang/id/settings.php @@ -264,6 +264,9 @@ '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' => 'Buat Token API', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/is/activities.php b/lang/is/activities.php index e70aa2567f0..7056e633c3d 100644 --- a/lang/is/activities.php +++ b/lang/is/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Tókst að uppfæra notanda', 'user_delete' => 'eyddur notandi', 'user_delete_notification' => 'Tókst að eyða notanda', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'API token búið til', diff --git a/lang/is/auth.php b/lang/is/auth.php index c5e7ce4f1d7..9b779aec7e7 100644 --- a/lang/is/auth.php +++ b/lang/is/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Þeesi auðkenning er ekki á skrá.', 'throttle' => 'Of margar tilraunir til innskráningar. Reyndu aftur eftir :seconds sekúndur.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Nýskrá', diff --git a/lang/is/entities.php b/lang/is/entities.php index 0d9a6fc1eb7..8735a18aea3 100644 --- a/lang/is/entities.php +++ b/lang/is/entities.php @@ -331,6 +331,9 @@ // 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', diff --git a/lang/is/settings.php b/lang/is/settings.php index 877e0dfa8ce..cabe31917ef 100644 --- a/lang/is/settings.php +++ b/lang/is/settings.php @@ -264,6 +264,9 @@ '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' => 'Búa til API tóka', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/it/activities.php b/lang/it/activities.php index d682ccca6e0..e0dba540383 100644 --- a/lang/it/activities.php +++ b/lang/it/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Utente aggiornato con successo', 'user_delete' => 'ha eliminato un utente', 'user_delete_notification' => 'Utente rimosso con successo', + 'user_mfa_reset' => 'resetta MFA per l\'utente', + 'user_mfa_reset_notification' => 'Metodi di autenticazione multi-fattore reimpostati', // API Tokens 'api_token_create' => 'ha creato un token API', diff --git a/lang/it/auth.php b/lang/it/auth.php index 9191ecba794..3dd5e1ec62e 100644 --- a/lang/it/auth.php +++ b/lang/it/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Credenziali errate.', 'throttle' => 'Troppi tentativi di login. Riprova in :seconds secondi.', + 'mfa_throttle' => 'Troppi tentativi di verifica multi-fattore. Riprova tra :seconds secondi.', // Login & Register 'sign_up' => 'Registrati', diff --git a/lang/it/entities.php b/lang/it/entities.php index 1cce5fcd54f..89e1cd98831 100644 --- a/lang/it/entities.php +++ b/lang/it/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Attiva/disattiva barra laterale', + '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' => 'Tag pagina', 'chapter_tags' => 'Tag capitolo', 'book_tags' => 'Tag libro', diff --git a/lang/it/settings.php b/lang/it/settings.php index 9a272db33d9..3b8681840f2 100644 --- a/lang/it/settings.php +++ b/lang/it/settings.php @@ -207,7 +207,7 @@ 'role_all' => 'Tutti', 'role_own' => 'Propri', 'role_controlled_by_asset' => 'Controllato dall\'entità in cui sono caricati', - 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', + 'role_controlled_by_page_delete' => 'Controllato dai permessi di cancellazione della pagina', 'role_save' => 'Salva ruolo', 'role_users' => 'Utenti in questo ruolo', 'role_users_none' => 'Nessun utente assegnato a questo ruolo', @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Imposta l\'autenticazione multi-fattore come misura di sicurezza aggiuntiva per il tuo account.', 'users_mfa_x_methods' => ':count metodo configurato|:count metodi configurati', 'users_mfa_configure' => 'Configura metodi', + 'users_mfa_reset' => 'Reimposta Metodi Di Autenticazione Multi-Fattore', + 'users_mfa_reset_desc' => 'Questo ripristinerà e cancellerà tutti i metodi di autenticazione multi-fattore configurati per questo utente. Se l\'autenticazione multi-fattore è richiesta da uno qualsiasi dei loro ruoli, sarà richiesto loro di configurare nuovi metodi al loro prossimo accesso.', + 'users_mfa_reset_confirm' => 'Sei sicuro di voler resettare l\'autenticazione multi-fattore per questo utente?', // API Tokens 'user_api_token_create' => 'Crea token API', @@ -364,6 +367,7 @@ 'sk' => 'Sloveno', 'sl' => 'Sloveno', 'sv' => 'Svedese', + 'th' => 'ภาษาไทย', 'tr' => 'Turco', 'uk' => 'Ucraino', 'uz' => 'O‘zbekcha', diff --git a/lang/ja/activities.php b/lang/ja/activities.php index 6f55098bbd9..25ded1d49b7 100644 --- a/lang/ja/activities.php +++ b/lang/ja/activities.php @@ -99,6 +99,8 @@ '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トークンを作成', diff --git a/lang/ja/auth.php b/lang/ja/auth.php index 077c31ac85c..b4ee5f4acf9 100644 --- a/lang/ja/auth.php +++ b/lang/ja/auth.php @@ -8,6 +8,7 @@ 'failed' => 'この資格情報は登録されていません。', 'throttle' => 'ログイン試行回数が制限を超えました。:seconds秒後に再試行してください。', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => '新規登録', diff --git a/lang/ja/entities.php b/lang/ja/entities.php index 57703997f3f..eaaddf5b3af 100644 --- a/lang/ja/entities.php +++ b/lang/ja/entities.php @@ -332,6 +332,9 @@ // 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' => 'ブックのタグ', diff --git a/lang/ja/settings.php b/lang/ja/settings.php index a3cbf696a18..95f33f34004 100644 --- a/lang/ja/settings.php +++ b/lang/ja/settings.php @@ -264,6 +264,9 @@ '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トークンの作成', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/ka/activities.php b/lang/ka/activities.php index 35730fd77a0..07304a56277 100644 --- a/lang/ka/activities.php +++ b/lang/ka/activities.php @@ -99,6 +99,8 @@ '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', diff --git a/lang/ka/auth.php b/lang/ka/auth.php index 57f0cb5c632..47be4ea721e 100644 --- a/lang/ka/auth.php +++ b/lang/ka/auth.php @@ -8,6 +8,7 @@ 'failed' => 'These credentials do not match our records.', 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Sign up', diff --git a/lang/ka/entities.php b/lang/ka/entities.php index 5501d2bc229..58c00ec4b27 100644 --- a/lang/ka/entities.php +++ b/lang/ka/entities.php @@ -331,6 +331,9 @@ // 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', diff --git a/lang/ka/settings.php b/lang/ka/settings.php index 3937c650f86..d03024a89d6 100644 --- a/lang/ka/settings.php +++ b/lang/ka/settings.php @@ -264,6 +264,9 @@ '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', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/ko/activities.php b/lang/ko/activities.php index 061b7fb1e5c..e011523face 100644 --- a/lang/ko/activities.php +++ b/lang/ko/activities.php @@ -99,6 +99,8 @@ '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 토큰', diff --git a/lang/ko/auth.php b/lang/ko/auth.php index af9129f14e1..9e93c884684 100644 --- a/lang/ko/auth.php +++ b/lang/ko/auth.php @@ -8,6 +8,7 @@ 'failed' => '자격 증명이 기록과 일치하지 않습니다.', 'throttle' => '로그인 시도가 너무 많습니다. :seconds초 후에 다시 시도해주세요.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => '가입', diff --git a/lang/ko/entities.php b/lang/ko/entities.php index c54d6c7dc05..838aebd7a69 100644 --- a/lang/ko/entities.php +++ b/lang/ko/entities.php @@ -331,6 +331,9 @@ // 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' => '책 태그', diff --git a/lang/ko/settings.php b/lang/ko/settings.php index 9aabe5c2a81..90d501a7cb0 100644 --- a/lang/ko/settings.php +++ b/lang/ko/settings.php @@ -264,6 +264,9 @@ '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 토큰 만들기', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/ku/activities.php b/lang/ku/activities.php index 4362fc02958..e9344a3d477 100644 --- a/lang/ku/activities.php +++ b/lang/ku/activities.php @@ -99,6 +99,8 @@ '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', diff --git a/lang/ku/auth.php b/lang/ku/auth.php index 369e06f93ed..5597683ae84 100644 --- a/lang/ku/auth.php +++ b/lang/ku/auth.php @@ -8,6 +8,7 @@ 'failed' => 'ئەم بەکارهێنەرە نەدۆزرایەوە.', 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Sign up', diff --git a/lang/ku/entities.php b/lang/ku/entities.php index 5501d2bc229..58c00ec4b27 100644 --- a/lang/ku/entities.php +++ b/lang/ku/entities.php @@ -331,6 +331,9 @@ // 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', diff --git a/lang/ku/settings.php b/lang/ku/settings.php index 3937c650f86..d03024a89d6 100644 --- a/lang/ku/settings.php +++ b/lang/ku/settings.php @@ -264,6 +264,9 @@ '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', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/lt/activities.php b/lang/lt/activities.php index a808171bb6d..0d08d41ead4 100644 --- a/lang/lt/activities.php +++ b/lang/lt/activities.php @@ -99,6 +99,8 @@ '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', diff --git a/lang/lt/auth.php b/lang/lt/auth.php index a2f7f466846..8a91f2104ec 100644 --- a/lang/lt/auth.php +++ b/lang/lt/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Šie įgaliojimai neatitinka mūsų įrašų.', 'throttle' => 'Per daug prisijungimo bandymų. Prašome pabandyti dar kartą po :seconds sekundžių.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Užsiregistruoti', diff --git a/lang/lt/entities.php b/lang/lt/entities.php index f6610a22a7d..6bf86635aca 100644 --- a/lang/lt/entities.php +++ b/lang/lt/entities.php @@ -331,6 +331,9 @@ // 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' => 'Puslapio žymos', 'chapter_tags' => 'Skyriaus žymos', 'book_tags' => 'Knygos žymos', diff --git a/lang/lt/settings.php b/lang/lt/settings.php index 23dd38d50e6..96ee2bebada 100644 --- a/lang/lt/settings.php +++ b/lang/lt/settings.php @@ -264,6 +264,9 @@ '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' => 'Sukurti API sąsajos prieigos raktą', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/lv/activities.php b/lang/lv/activities.php index 38d9ae21766..8f902517031 100644 --- a/lang/lv/activities.php +++ b/lang/lv/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Lietotājs veiksmīgi atjaunināts', 'user_delete' => 'dzēsa lietotāju', 'user_delete_notification' => 'Lietotājs veiksmīgi dzēsts', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'izveidoja API žetonu', diff --git a/lang/lv/auth.php b/lang/lv/auth.php index 71b92ac0109..462ce110f8b 100644 --- a/lang/lv/auth.php +++ b/lang/lv/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Šie reģistrācijas dati neatbilst mūsu ierakstiem.', 'throttle' => 'Pārāk daudz pieteikšanās mēģinājumu. Lūdzu, mēģiniet vēlreiz pēc :seconds seconds.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Reģistrēties', diff --git a/lang/lv/entities.php b/lang/lv/entities.php index f0f32ab2174..dccab4bd114 100644 --- a/lang/lv/entities.php +++ b/lang/lv/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Pārslēgt sānjoslu', + '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' => 'Lapas birkas', 'chapter_tags' => 'Nodaļas birkas', 'book_tags' => 'Grāmatas birkas', diff --git a/lang/lv/settings.php b/lang/lv/settings.php index 0e11ebd65d6..886e0ef0a0c 100644 --- a/lang/lv/settings.php +++ b/lang/lv/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Iestati vairākfaktoru autentifikāciju kā papildus drošības līmeni tavam lietotāja kontam.', 'users_mfa_x_methods' => ':count metode iestatīta|:count metodes iestatītas', 'users_mfa_configure' => 'Iestatīt metodes', + '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' => 'Izveidot API žetonu', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/nb/activities.php b/lang/nb/activities.php index fd4de53216e..771f5438baa 100644 --- a/lang/nb/activities.php +++ b/lang/nb/activities.php @@ -100,6 +100,8 @@ 'user_update_notification' => 'Brukeren ble oppdatert', 'user_delete' => 'slettet bruker', 'user_delete_notification' => 'Brukeren ble fjernet', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'opprettet API-nøkkel', diff --git a/lang/nb/auth.php b/lang/nb/auth.php index 125e6daf5d4..f345f691ac9 100644 --- a/lang/nb/auth.php +++ b/lang/nb/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Disse detaljene samsvarer ikke med det vi har på bok.', 'throttle' => 'For mange forsøk, prøv igjen om :seconds sekunder.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registrer deg', diff --git a/lang/nb/entities.php b/lang/nb/entities.php index a67c35e1748..0e9070e53d5 100644 --- a/lang/nb/entities.php +++ b/lang/nb/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Bytt sidestolpe', + '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' => 'Sidemerker', 'chapter_tags' => 'Kapittelmerker', 'book_tags' => 'Bokmerker', diff --git a/lang/nb/settings.php b/lang/nb/settings.php index 5e779beead5..1cc5d8e02ca 100644 --- a/lang/nb/settings.php +++ b/lang/nb/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Konfigurer flerfaktorautentisering som et ekstra lag med sikkerhet for din konto.', 'users_mfa_x_methods' => ':count metode konfigurert|:count metoder konfigurert', 'users_mfa_configure' => 'Konfigurer metoder', + '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' => 'Opprett API-nøkkel', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/ne/activities.php b/lang/ne/activities.php index 4b4edf7be3a..24c2b948a4b 100644 --- a/lang/ne/activities.php +++ b/lang/ne/activities.php @@ -99,6 +99,8 @@ '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 टोकन सिर्जना गरियो', diff --git a/lang/ne/auth.php b/lang/ne/auth.php index cd875a226cb..378f2dea28e 100644 --- a/lang/ne/auth.php +++ b/lang/ne/auth.php @@ -8,6 +8,7 @@ 'failed' => 'यी प्रमाणिकरण जानकारी हाम्रो अभिलेखसँग मेल खाँदैन।', 'throttle' => 'लगइन प्रयासहरूको संख्या धेरै भएको छ। कृपया :seconds सेकेन्ड पछि पुनः प्रयास गर्नुहोस्।', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'साइन अप गर्नुहोस्', diff --git a/lang/ne/entities.php b/lang/ne/entities.php index 4d9f78ea504..f51c265c576 100644 --- a/lang/ne/entities.php +++ b/lang/ne/entities.php @@ -331,6 +331,9 @@ // 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' => 'पुस्तक ट्यागहरू', diff --git a/lang/ne/settings.php b/lang/ne/settings.php index f52bcd42026..549a4dc8b73 100644 --- a/lang/ne/settings.php +++ b/lang/ne/settings.php @@ -264,6 +264,9 @@ '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 टोकन सिर्जना गर्नुहोस्', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/nl/activities.php b/lang/nl/activities.php index 230c7a58b3a..55356966ddc 100644 --- a/lang/nl/activities.php +++ b/lang/nl/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Gebruiker succesvol bijgewerkt', 'user_delete' => 'verwijderde gebruiker', 'user_delete_notification' => 'Gebruiker succesvol verwijderd', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'API-token aangemaakt', diff --git a/lang/nl/auth.php b/lang/nl/auth.php index 12c6b6c7bff..49d04dd4fbf 100644 --- a/lang/nl/auth.php +++ b/lang/nl/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Deze inloggegevens zijn niet bij ons bekend.', 'throttle' => 'Te veel inlogpogingen! Probeer het opnieuw na :seconds seconden.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registreer', diff --git a/lang/nl/entities.php b/lang/nl/entities.php index c39beef2de6..e90aff57573 100644 --- a/lang/nl/entities.php +++ b/lang/nl/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Zijbalk Tonen/Verbergen', + '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' => 'Pagina Labels', 'chapter_tags' => 'Hoofdstuk Labels', 'book_tags' => 'Boek Labels', diff --git a/lang/nl/settings.php b/lang/nl/settings.php index b82c52debba..9b0fe5ad154 100644 --- a/lang/nl/settings.php +++ b/lang/nl/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Stel meervoudige verificatie in als extra beveiligingslaag voor je gebruikersaccount.', 'users_mfa_x_methods' => ':count methode geconfigureerd|:count methoden geconfigureerd', 'users_mfa_configure' => 'Configureer methoden', + '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-token aanmaken', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky (Slowaaks)', 'sl' => 'Slovenščina (Sloveens)', 'sv' => 'Svenska (Zweeds)', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe (Turks)', 'uk' => 'Українська (Oekraïens)', 'uz' => 'Oezbeeks', diff --git a/lang/nn/activities.php b/lang/nn/activities.php index f87aadc6677..fa934bf45fb 100644 --- a/lang/nn/activities.php +++ b/lang/nn/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Brukaren vart oppdatert', 'user_delete' => 'sletta brukar', 'user_delete_notification' => 'Brukaren vart fjerna', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'opprett API-nøkkel', diff --git a/lang/nn/auth.php b/lang/nn/auth.php index 5da686eaa9e..7e53aaee02c 100644 --- a/lang/nn/auth.php +++ b/lang/nn/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Desse detaljane samsvarar ikkje med det me har på bok.', 'throttle' => 'For mange forsøk, prøv på nytt om :seconds sekunder.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registrer deg', diff --git a/lang/nn/entities.php b/lang/nn/entities.php index 4f79f3d7518..878e2ef0faa 100644 --- a/lang/nn/entities.php +++ b/lang/nn/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Vis/gøym sidepanelet', + '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' => 'Sidemerker', 'chapter_tags' => 'Kapittelmerker', 'book_tags' => 'Bokmerker', diff --git a/lang/nn/settings.php b/lang/nn/settings.php index b6c322c9c34..08709833273 100644 --- a/lang/nn/settings.php +++ b/lang/nn/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Konfigurer flerfaktorautentisering som eit ekstra lag med tryggleik for din konto.', 'users_mfa_x_methods' => ':count metode konfigurert|:count metoder konfigurert', 'users_mfa_configure' => 'Konfigurer metoder', + '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' => 'Opprett API-nøkkel', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/pl/activities.php b/lang/pl/activities.php index 323b7035e1b..00687187d43 100644 --- a/lang/pl/activities.php +++ b/lang/pl/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Użytkownik zaktualizowany pomyślnie', 'user_delete' => 'usunięto użytkownika', 'user_delete_notification' => 'Użytkownik pomyślnie usunięty', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'utworzono token API', diff --git a/lang/pl/auth.php b/lang/pl/auth.php index 445f8198af8..7b4997a6cb0 100644 --- a/lang/pl/auth.php +++ b/lang/pl/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Wprowadzone poświadczenia są nieprawidłowe.', 'throttle' => 'Zbyt wiele prób logowania. Spróbuj ponownie za :seconds s.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Zarejestruj się', diff --git a/lang/pl/entities.php b/lang/pl/entities.php index 88e4344de40..ea0ac73670e 100644 --- a/lang/pl/entities.php +++ b/lang/pl/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Przełącz pasek boczny', + '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' => 'Tagi strony', 'chapter_tags' => 'Tagi rozdziału', 'book_tags' => 'Tagi książki', diff --git a/lang/pl/settings.php b/lang/pl/settings.php index 98201406e7b..bfa6339d551 100644 --- a/lang/pl/settings.php +++ b/lang/pl/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Skonfiguruj uwierzytelnianie wieloskładnikowe jako dodatkową warstwę bezpieczeństwa dla swojego konta użytkownika.', 'users_mfa_x_methods' => ':count metoda skonfigurowana|:count metody skonfigurowane', 'users_mfa_configure' => 'Konfiguruj metody', + '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' => 'Utwórz klucz API', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/pt/activities.php b/lang/pt/activities.php index 33d9e2ca686..e2ed42c7111 100644 --- a/lang/pt/activities.php +++ b/lang/pt/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Utilizador atualizado com sucesso', 'user_delete' => 'utilizador eliminado', 'user_delete_notification' => 'Utilizador removido com sucesso', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'token API criado', diff --git a/lang/pt/auth.php b/lang/pt/auth.php index 8807e1a87d3..453b201689d 100644 --- a/lang/pt/auth.php +++ b/lang/pt/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Estas credenciais não coincidem com os nossos registos.', 'throttle' => 'Demasiadas tentativas de acesso. Tente novamente em :seconds segundos.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registar', diff --git a/lang/pt/entities.php b/lang/pt/entities.php index 709b8466aa7..3bdaeb12c8d 100644 --- a/lang/pt/entities.php +++ b/lang/pt/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Alternar 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' => 'Etiquetas de Página', 'chapter_tags' => 'Etiquetas do Capítulo', 'book_tags' => 'Etiquetas do Livro', diff --git a/lang/pt/settings.php b/lang/pt/settings.php index 47fecbcbfee..1488db87a73 100644 --- a/lang/pt/settings.php +++ b/lang/pt/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Configure a autenticação multi-fatores como uma camada extra de segurança para sua conta de utilizador.', 'users_mfa_x_methods' => ':count método configurado|:count métodos configurados', 'users_mfa_configure' => 'Configurar Métodos', + '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' => 'Criar Token de API', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/pt_BR/activities.php b/lang/pt_BR/activities.php index 2a2d173c790..e9069564f50 100644 --- a/lang/pt_BR/activities.php +++ b/lang/pt_BR/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Usuário atualizado com sucesso', 'user_delete' => 'usuário excluído', 'user_delete_notification' => 'Usuário removido com sucesso', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'token de API criado', diff --git a/lang/pt_BR/auth.php b/lang/pt_BR/auth.php index 0580542b49b..dfcc47da6f8 100644 --- a/lang/pt_BR/auth.php +++ b/lang/pt_BR/auth.php @@ -8,6 +8,7 @@ 'failed' => 'As credenciais fornecidas não puderam ser validadas em nossos registros.', 'throttle' => 'Muitas tentativas de login. Por favor, tente novamente em :seconds segundos.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Criar Conta', diff --git a/lang/pt_BR/entities.php b/lang/pt_BR/entities.php index 0211f45fa7c..7fa0b50c1d1 100644 --- a/lang/pt_BR/entities.php +++ b/lang/pt_BR/entities.php @@ -331,6 +331,9 @@ // 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' => 'Marcadores de Página', 'chapter_tags' => 'Marcadores de Capítulo', 'book_tags' => 'Marcadores de Livro', diff --git a/lang/pt_BR/settings.php b/lang/pt_BR/settings.php index 9b7c6a7d4f9..8129637c8dc 100644 --- a/lang/pt_BR/settings.php +++ b/lang/pt_BR/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'A autenticação multi-fator adiciona outra camada de segurança à sua conta.', 'users_mfa_x_methods' => ':count método configurado|:count métodos configurados', 'users_mfa_configure' => 'Configurar Métodos', + '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' => 'Criar Token de API', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/ro/activities.php b/lang/ro/activities.php index eed5cc0832f..c00976cf710 100644 --- a/lang/ro/activities.php +++ b/lang/ro/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Utilizator actualizat cu succes', 'user_delete' => 'utilizator șters', 'user_delete_notification' => 'Utilizator eliminat cu succes', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'created API token', diff --git a/lang/ro/auth.php b/lang/ro/auth.php index e02eeb4c1ba..73e16538183 100644 --- a/lang/ro/auth.php +++ b/lang/ro/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Aceste credenţiale nu se potrivesc cu înregistrările noastre.', 'throttle' => 'Prea multe încercări de conectare. Vă rugăm să încercați din nou în :seconds secunde.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Inregistrează-te', diff --git a/lang/ro/entities.php b/lang/ro/entities.php index aa18d09fba7..18ffd30c9e8 100644 --- a/lang/ro/entities.php +++ b/lang/ro/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Comutați bara 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' => 'Etichete pagină', 'chapter_tags' => 'Etichete capitol', 'book_tags' => 'Etichete carte', diff --git a/lang/ro/settings.php b/lang/ro/settings.php index 117c4ca611a..bbbaa34d959 100644 --- a/lang/ro/settings.php +++ b/lang/ro/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Configurare autentificarea multi-factor ca un nivel suplimentar de securitate pentru contul tău de utilizator.', 'users_mfa_x_methods' => ':count metodă configurată|:count metode configurate', 'users_mfa_configure' => 'Configurare metode', + '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' => 'Creare token API', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/ru/activities.php b/lang/ru/activities.php index d9ac1685ba9..9d08d1d4a19 100644 --- a/lang/ru/activities.php +++ b/lang/ru/activities.php @@ -99,6 +99,8 @@ '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 токен', diff --git a/lang/ru/auth.php b/lang/ru/auth.php index d39da119dea..433bfbe5fd6 100644 --- a/lang/ru/auth.php +++ b/lang/ru/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Введенные вами данные не найдены в нашей базе.', 'throttle' => 'Слишком много попыток входа. Пожалуйста, повторите попытку через :seconds секунд.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Регистрация', diff --git a/lang/ru/entities.php b/lang/ru/entities.php index 28d096fe802..d3c4230acd2 100644 --- a/lang/ru/entities.php +++ b/lang/ru/entities.php @@ -331,6 +331,9 @@ // 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' => 'Теги книги', diff --git a/lang/ru/settings.php b/lang/ru/settings.php index 7bf5832a369..4fdd5784ab2 100644 --- a/lang/ru/settings.php +++ b/lang/ru/settings.php @@ -264,6 +264,9 @@ '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' => 'Создать токен', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/sk/activities.php b/lang/sk/activities.php index 65542fbe2a1..b9a532e2e05 100644 --- a/lang/sk/activities.php +++ b/lang/sk/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Používateľ úspešne upravený', 'user_delete' => 'odstránený používateľ', 'user_delete_notification' => 'Používateľ úspešne zmazaný', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'created API token', diff --git a/lang/sk/auth.php b/lang/sk/auth.php index 11ccdc8893f..52f34f68f28 100644 --- a/lang/sk/auth.php +++ b/lang/sk/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Tieto údaje sa nezhodujú s našimi záznamami.', 'throttle' => 'Priveľa pokusov o prihlásenie. Skúste znova o :seconds sekúnd.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registrácia', diff --git a/lang/sk/entities.php b/lang/sk/entities.php index 48b662bf388..697aef0b875 100644 --- a/lang/sk/entities.php +++ b/lang/sk/entities.php @@ -331,6 +331,9 @@ // 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' => 'Štítky stránok', 'chapter_tags' => 'Štítky kapitol', 'book_tags' => 'Štítky kníh', diff --git a/lang/sk/settings.php b/lang/sk/settings.php index e18801ff467..e76fefb98e8 100644 --- a/lang/sk/settings.php +++ b/lang/sk/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Pre vyššiu úroveň bezpečnosti si nastavte viacúrovňové prihlasovanie.', 'users_mfa_x_methods' => ':count nakonfigurované metódy|:count nakonfigurovaných metód', 'users_mfa_configure' => 'Konfigurovať metódy', + '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' => 'Vytvoriť API token', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/sl/activities.php b/lang/sl/activities.php index 117c73ca186..3b8977c2f9a 100644 --- a/lang/sl/activities.php +++ b/lang/sl/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Uporabnik uspešno posodobljen', 'user_delete' => 'uporabnik izbrisan', 'user_delete_notification' => 'Uporabnik uspešno izbrisan', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'ustvarjen žeton API', diff --git a/lang/sl/auth.php b/lang/sl/auth.php index e06d823e7cd..46c6a565d24 100644 --- a/lang/sl/auth.php +++ b/lang/sl/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Poverilnice se ne ujemajo s podatki v naši bazi.', 'throttle' => 'Prekoračili ste število možnih prijav. Poskusite znova čez :seconds sekund.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Registracija', diff --git a/lang/sl/entities.php b/lang/sl/entities.php index 86a43132ef6..0efbc6d17c9 100644 --- a/lang/sl/entities.php +++ b/lang/sl/entities.php @@ -331,6 +331,9 @@ // 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 strani', 'chapter_tags' => 'Oznake poglavja', 'book_tags' => 'Oznake knjige', diff --git a/lang/sl/settings.php b/lang/sl/settings.php index 87c1e8e6db2..9bd63a0e88e 100644 --- a/lang/sl/settings.php +++ b/lang/sl/settings.php @@ -264,6 +264,9 @@ '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' => 'Ustvari žeton', @@ -365,6 +368,7 @@ 'sk' => 'Slovensky', 'sl' => 'slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/sq/activities.php b/lang/sq/activities.php index 9296d1759ca..2d41a11218b 100644 --- a/lang/sq/activities.php +++ b/lang/sq/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Përdoruesi u përditësua me sukses', 'user_delete' => 'fshi përdorues', 'user_delete_notification' => 'Përdoruesi u fshi me sukses', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'Krijoi token API', diff --git a/lang/sq/auth.php b/lang/sq/auth.php index 6fdc4ee4a6d..d5bf7acfb58 100644 --- a/lang/sq/auth.php +++ b/lang/sq/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Këto kredenciale nuk përputhen me të dhënat tona.', 'throttle' => 'Shumë përpjekje për hyrje. Ju lutemi provoni përsëri në :seconds sekonda.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Regjistrohu', diff --git a/lang/sq/entities.php b/lang/sq/entities.php index 5501d2bc229..58c00ec4b27 100644 --- a/lang/sq/entities.php +++ b/lang/sq/entities.php @@ -331,6 +331,9 @@ // 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', diff --git a/lang/sq/settings.php b/lang/sq/settings.php index 3937c650f86..d03024a89d6 100644 --- a/lang/sq/settings.php +++ b/lang/sq/settings.php @@ -264,6 +264,9 @@ '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', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/sr/activities.php b/lang/sr/activities.php index b4aa5e2a7f4..555d01adca1 100644 --- a/lang/sr/activities.php +++ b/lang/sr/activities.php @@ -99,6 +99,8 @@ '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' => 'креирао апи токен', diff --git a/lang/sr/auth.php b/lang/sr/auth.php index 0100e666fa3..96169fe2bab 100644 --- a/lang/sr/auth.php +++ b/lang/sr/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Ови акредитиви се не поклапају са нашом евиденцијом.', 'throttle' => 'Превише покушаја пријаве. Покушајте поново за :seconds секунди.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Региструј се', diff --git a/lang/sr/entities.php b/lang/sr/entities.php index 8f9c40e91a3..cd4e732f412 100644 --- a/lang/sr/entities.php +++ b/lang/sr/entities.php @@ -331,6 +331,9 @@ // 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', diff --git a/lang/sr/settings.php b/lang/sr/settings.php index 3453bc344b3..143fdaef19e 100644 --- a/lang/sr/settings.php +++ b/lang/sr/settings.php @@ -264,6 +264,9 @@ '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', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/sv/activities.php b/lang/sv/activities.php index 3acf6d4ff8d..c501c675273 100644 --- a/lang/sv/activities.php +++ b/lang/sv/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Användaren har uppdaterats', 'user_delete' => 'raderad användare', 'user_delete_notification' => 'Användaren har tagits bort', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'skapade API-token', diff --git a/lang/sv/auth.php b/lang/sv/auth.php index c9feb831218..6c94f21f9e0 100644 --- a/lang/sv/auth.php +++ b/lang/sv/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Uppgifterna stämmer inte överens med våra register.', 'throttle' => 'För många inloggningsförsök. Prova igen om :seconds sekunder.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Skapa konto', diff --git a/lang/sv/entities.php b/lang/sv/entities.php index 94fc23f501a..1df81684913 100644 --- a/lang/sv/entities.php +++ b/lang/sv/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Visa/Dölj sidopanel', + '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' => 'Sidtaggar', 'chapter_tags' => 'Kapiteltaggar', 'book_tags' => 'Boktaggar', diff --git a/lang/sv/settings.php b/lang/sv/settings.php index 47f602b8df8..e900a86a3ba 100644 --- a/lang/sv/settings.php +++ b/lang/sv/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Konfigurera multifaktorsautentisering som ett extra skydd för ditt konto.', 'users_mfa_x_methods' => ':count metod konfigurerad|:count metoder konfigurerade', 'users_mfa_configure' => 'Konfigurera metoder', + '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' => 'Skapa API-nyckel', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenska', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/th/activities.php b/lang/th/activities.php index 6d84813d6ac..0ea18ca0a6b 100644 --- a/lang/th/activities.php +++ b/lang/th/activities.php @@ -99,6 +99,8 @@ '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 Token', diff --git a/lang/th/auth.php b/lang/th/auth.php index c00b29bcd88..ca24b593c47 100644 --- a/lang/th/auth.php +++ b/lang/th/auth.php @@ -8,6 +8,7 @@ 'failed' => 'ข้อมูลประจำตัวไม่ตรงกับที่มีในระบบ', 'throttle' => 'เข้าสู่ระบบล้มเหลวหลายครั้งเกินไป กรุณาลองใหม่ในอีก :seconds วินาที', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'สมัครสมาชิก', diff --git a/lang/th/entities.php b/lang/th/entities.php index 1f0f30ec6ff..bda46b62c59 100644 --- a/lang/th/entities.php +++ b/lang/th/entities.php @@ -331,6 +331,9 @@ // 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' => 'แท็กหนังสือ', diff --git a/lang/th/settings.php b/lang/th/settings.php index 1b90d2a7196..558d2da0f6e 100644 --- a/lang/th/settings.php +++ b/lang/th/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'ตั้งค่าการยืนยันตัวตนแบบหลายขั้นตอนเป็นชั้นความปลอดภัยเพิ่มเติมสำหรับบัญชีผู้ใช้ของคุณ', 'users_mfa_x_methods' => 'กำหนดค่า :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 Token', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/tk/activities.php b/lang/tk/activities.php index 4362fc02958..e9344a3d477 100644 --- a/lang/tk/activities.php +++ b/lang/tk/activities.php @@ -99,6 +99,8 @@ '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', diff --git a/lang/tk/auth.php b/lang/tk/auth.php index 57f0cb5c632..47be4ea721e 100644 --- a/lang/tk/auth.php +++ b/lang/tk/auth.php @@ -8,6 +8,7 @@ 'failed' => 'These credentials do not match our records.', 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Sign up', diff --git a/lang/tk/entities.php b/lang/tk/entities.php index 5501d2bc229..58c00ec4b27 100644 --- a/lang/tk/entities.php +++ b/lang/tk/entities.php @@ -331,6 +331,9 @@ // 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', diff --git a/lang/tk/settings.php b/lang/tk/settings.php index 3937c650f86..d03024a89d6 100644 --- a/lang/tk/settings.php +++ b/lang/tk/settings.php @@ -264,6 +264,9 @@ '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', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/tr/activities.php b/lang/tr/activities.php index 5bed9ea303b..71282e448d7 100644 --- a/lang/tr/activities.php +++ b/lang/tr/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Kullanıcı başarıyla güncellendi', 'user_delete' => 'kullanıcı silindi', 'user_delete_notification' => 'Kullanıcı başarıyla silindi', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'created API token', diff --git a/lang/tr/auth.php b/lang/tr/auth.php index c9663f21e00..f8942a4dfc3 100644 --- a/lang/tr/auth.php +++ b/lang/tr/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Girdiğiniz bilgiler kayıtlarımızla uyuşmuyor.', 'throttle' => 'Çok fazla giriş yapmaya çalıştınız. Lütfen :seconds saniye içinde tekrar deneyin.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Kaydol', diff --git a/lang/tr/entities.php b/lang/tr/entities.php index 3d271ecb52f..e5bdacae791 100644 --- a/lang/tr/entities.php +++ b/lang/tr/entities.php @@ -331,6 +331,9 @@ // 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' => 'Sayfa Etiketleri', 'chapter_tags' => 'Bölüm Etiketleri', 'book_tags' => 'Kitap Etiketleri', diff --git a/lang/tr/settings.php b/lang/tr/settings.php index af8d2c6494b..39ae23f2845 100644 --- a/lang/tr/settings.php +++ b/lang/tr/settings.php @@ -264,6 +264,9 @@ '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' => 'Yöntemleri Yapılandır', + '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 Anahtarı Oluştur', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovence', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/uk/activities.php b/lang/uk/activities.php index e8b77e0d430..140fec47c7e 100644 --- a/lang/uk/activities.php +++ b/lang/uk/activities.php @@ -99,6 +99,8 @@ '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 токен', diff --git a/lang/uk/auth.php b/lang/uk/auth.php index 27aae39367e..cb927cfd47f 100644 --- a/lang/uk/auth.php +++ b/lang/uk/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Цей обліковий запис не знайдено.', 'throttle' => 'Забагато спроб входу в систему. Будь ласка, спробуйте ще раз через :seconds секунд.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Реєстрація', diff --git a/lang/uk/entities.php b/lang/uk/entities.php index 6bf7b241ef1..a77b8e77d13 100644 --- a/lang/uk/entities.php +++ b/lang/uk/entities.php @@ -331,6 +331,9 @@ // 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' => 'Теги книг', diff --git a/lang/uk/settings.php b/lang/uk/settings.php index 9706b328b3b..02fd6a4f6ee 100644 --- a/lang/uk/settings.php +++ b/lang/uk/settings.php @@ -264,6 +264,9 @@ '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', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/uz/activities.php b/lang/uz/activities.php index 84764192d77..f87a52870fa 100644 --- a/lang/uz/activities.php +++ b/lang/uz/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Foydalanuvchi muvaffaqiyatli yangilandi', 'user_delete' => 'deleted user', 'user_delete_notification' => 'Foydalanuvchi muvaffaqiyatli olib tashlandi', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'created API token', diff --git a/lang/uz/auth.php b/lang/uz/auth.php index 59a620daeca..1c31d8f870a 100644 --- a/lang/uz/auth.php +++ b/lang/uz/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Uchbu ma‘lumotlar, bizdagi ma‘lumotlarga mos kelmadi.', 'throttle' => 'Kirishga urinishlar juda ko‘p. Iltimos :seconds soniyadan so‘ng urinib ko‘ring.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Ro‘yxatdan o‘tish', diff --git a/lang/uz/entities.php b/lang/uz/entities.php index d3fdb594421..f8702892617 100644 --- a/lang/uz/entities.php +++ b/lang/uz/entities.php @@ -331,6 +331,9 @@ // 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' => 'Sahifa teglari', 'chapter_tags' => 'Bo\'lim teglari', 'book_tags' => 'Kitob teglari', diff --git a/lang/uz/settings.php b/lang/uz/settings.php index 0dbf8351e83..c1d3b3d46e1 100644 --- a/lang/uz/settings.php +++ b/lang/uz/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Ko\'p faktorli autentifikatsiyani foydalanuvchi hisobingiz uchun qo\'shimcha xavfsizlik qatlami sifatida o\'rnating.', 'users_mfa_x_methods' => ':count usuli tuzilgan|:count usullari sozlangan', 'users_mfa_configure' => 'Usullarni sozlash', + '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 tokenini yarating', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/vi/activities.php b/lang/vi/activities.php index 00ec6ce04d5..0625bb529a5 100644 --- a/lang/vi/activities.php +++ b/lang/vi/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => 'Người dùng được cập nhật thành công', 'user_delete' => 'người dùng đã bị xóa', 'user_delete_notification' => 'Người dùng đã được xóa thành công', + 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens 'api_token_create' => 'Đã tạo Token API ', diff --git a/lang/vi/auth.php b/lang/vi/auth.php index d53d2cf4cb6..9e0ce731b25 100644 --- a/lang/vi/auth.php +++ b/lang/vi/auth.php @@ -8,6 +8,7 @@ 'failed' => 'Thông tin đăng nhập này không khớp với dữ liệu của chúng tôi.', 'throttle' => 'Quá nhiều lần đăng nhập sai. Vui lòng thử lại sau :seconds giây.', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => 'Đăng ký', diff --git a/lang/vi/entities.php b/lang/vi/entities.php index fc1ca6d5620..a3f92e03024 100644 --- a/lang/vi/entities.php +++ b/lang/vi/entities.php @@ -331,6 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Chuyển đổi thanh bên', + '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' => 'Các Thẻ Trang', 'chapter_tags' => 'Các Thẻ Chương', 'book_tags' => 'Các Thẻ Sách', diff --git a/lang/vi/settings.php b/lang/vi/settings.php index c0bca56914c..d029fe7f329 100644 --- a/lang/vi/settings.php +++ b/lang/vi/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => 'Thiết lập xác thực đa yếu tố như một lớp bảo mật bổ sung cho tài khoản người dùng của bạn.', 'users_mfa_x_methods' => ':count phương thức đã cấu hình|:count phương thức đã cấu hình', 'users_mfa_configure' => 'Cấu hình phương thức', + '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' => 'Tạo Token API', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/zh_CN/activities.php b/lang/zh_CN/activities.php index a2a2acd24f2..d30e61f0f2a 100644 --- a/lang/zh_CN/activities.php +++ b/lang/zh_CN/activities.php @@ -99,6 +99,8 @@ 'user_update_notification' => '用户更新成功', 'user_delete' => '用户已删除', 'user_delete_notification' => '成功移除用户', + 'user_mfa_reset' => '为用户重置MFA', + 'user_mfa_reset_notification' => '多因素认证方法已重置', // API Tokens 'api_token_create' => '已创建 API 令牌', diff --git a/lang/zh_CN/auth.php b/lang/zh_CN/auth.php index 8c4ba63f9e2..4c97b46ce02 100644 --- a/lang/zh_CN/auth.php +++ b/lang/zh_CN/auth.php @@ -8,6 +8,7 @@ 'failed' => '用户名或密码错误。', 'throttle' => '您的登录次数过多,请在:seconds秒后重试。', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => '注册', diff --git a/lang/zh_CN/entities.php b/lang/zh_CN/entities.php index 9d57eeb5719..9daf816da04 100644 --- a/lang/zh_CN/entities.php +++ b/lang/zh_CN/entities.php @@ -331,6 +331,9 @@ // 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' => '书籍标签', diff --git a/lang/zh_CN/settings.php b/lang/zh_CN/settings.php index 86dd680b02c..eef14b1687d 100644 --- a/lang/zh_CN/settings.php +++ b/lang/zh_CN/settings.php @@ -264,6 +264,9 @@ 'users_mfa_desc' => '设置多重身份认证能增加您账户的安全性。', 'users_mfa_x_methods' => ':count 个措施已配置|:count 个措施已配置', 'users_mfa_configure' => '配置安全措施', + 'users_mfa_reset' => '重置多重身份验证方法', + 'users_mfa_reset_desc' => '此操作将重置并清除该用户所有已配置的多重身份验证方法。如果多重身份验证是任何角色所要求的,用户在下次登录时将被提示配置新的方法。', + 'users_mfa_reset_confirm' => '您确定要重置此用户的多重身份验证吗?', // API Tokens 'user_api_token_create' => '创建 API 令牌', @@ -364,6 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/zh_TW/activities.php b/lang/zh_TW/activities.php index 65ba0a56597..791fa26b752 100644 --- a/lang/zh_TW/activities.php +++ b/lang/zh_TW/activities.php @@ -99,6 +99,8 @@ '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 權杖', diff --git a/lang/zh_TW/auth.php b/lang/zh_TW/auth.php index 1168b48b270..47e8ed950b2 100644 --- a/lang/zh_TW/auth.php +++ b/lang/zh_TW/auth.php @@ -8,6 +8,7 @@ 'failed' => '使用者名稱或密碼錯誤。', 'throttle' => '您的登入次數過多,請在 :seconds 秒後重試。', + 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', // Login & Register 'sign_up' => '註冊', diff --git a/lang/zh_TW/entities.php b/lang/zh_TW/entities.php index 08886013c43..ba53d885dd4 100644 --- a/lang/zh_TW/entities.php +++ b/lang/zh_TW/entities.php @@ -331,6 +331,9 @@ // 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' => '書本標籤', diff --git a/lang/zh_TW/settings.php b/lang/zh_TW/settings.php index fd5b088f5f8..95108545019 100644 --- a/lang/zh_TW/settings.php +++ b/lang/zh_TW/settings.php @@ -265,6 +265,9 @@ '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 權杖', @@ -365,6 +368,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', + 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', From 67529dd883cf9e70f8e362a9fcfc6edcc4d19a96 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 28 May 2026 10:34:04 +0100 Subject: [PATCH 173/204] Deps: Updated PHP packages, fixed some types for phpstan --- app/Search/SearchIndex.php | 10 +- composer.lock | 279 +++++++++++++++++++------------------ 2 files changed, 150 insertions(+), 139 deletions(-) diff --git a/app/Search/SearchIndex.php b/app/Search/SearchIndex.php index ce78831eeae..e2a4a21a472 100644 --- a/app/Search/SearchIndex.php +++ b/app/Search/SearchIndex.php @@ -119,7 +119,7 @@ protected function insertTerms(array $terms): void * Create a scored term array from the given text, where the keys are the terms * and the values are their scores. * - * @return array + * @return array */ protected function generateTermScoreMapFromText(string $text, float $scoreAdjustment = 1): array { @@ -136,7 +136,7 @@ protected function generateTermScoreMapFromText(string $text, float $scoreAdjust * Create a scored term array from the given HTML, where the keys are the terms * and the values are their scores. * - * @return array + * @return array */ protected function generateTermScoreMapFromHtml(string $html): array { @@ -177,7 +177,7 @@ protected function generateTermScoreMapFromHtml(string $html): array * * @param Tag[] $tags * - * @return array + * @return array */ protected function generateTermScoreMapFromTags(array $tags): array { @@ -277,9 +277,9 @@ protected function entityToTermDataArray(Entity $entity): array * For the given term data arrays, Merge their contents by term * while combining any scores. * - * @param array[] ...$scoreMaps + * @param array[] ...$scoreMaps * - * @return array + * @return array */ protected function mergeTermScoreMaps(...$scoreMaps): array { diff --git a/composer.lock b/composer.lock index c0346a7b27c..710067eb0a6 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.381.5", + "version": "3.382.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "409208d62af0ddafbcb0af1a0bf514f5ffcaba92" + "reference": "6844cc6421c47d6b96633ab8039045012acbeb27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/409208d62af0ddafbcb0af1a0bf514f5ffcaba92", - "reference": "409208d62af0ddafbcb0af1a0bf514f5ffcaba92", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6844cc6421c47d6b96633ab8039045012acbeb27", + "reference": "6844cc6421c47d6b96633ab8039045012acbeb27", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.381.5" + "source": "https://github.com/aws/aws-sdk-php/tree/3.382.2" }, - "time": "2026-05-20T18:16:01+00:00" + "time": "2026-05-27T18:11:41+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1179,16 +1179,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.10.3", + "version": "7.10.5", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "47ba23c7a55247e2e1b7407aca90e9bbed0d9d86" + "reference": "7c8d84b39e680315f687e8662a9d6fb0865c5148" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/47ba23c7a55247e2e1b7407aca90e9bbed0d9d86", - "reference": "47ba23c7a55247e2e1b7407aca90e9bbed0d9d86", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7c8d84b39e680315f687e8662a9d6fb0865c5148", + "reference": "7c8d84b39e680315f687e8662a9d6fb0865c5148", "shasum": "" }, "require": { @@ -1206,7 +1206,7 @@ "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", "guzzle/client-integration-tests": "3.0.2", - "guzzlehttp/test-server": "^0.3.2", + "guzzlehttp/test-server": "^0.4", "php-http/message-factory": "^1.1", "phpunit/phpunit": "^8.5.52 || ^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" @@ -1286,7 +1286,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.3" + "source": "https://github.com/guzzle/guzzle/tree/7.10.5" }, "funding": [ { @@ -1302,7 +1302,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T22:59:19+00:00" + "time": "2026-05-27T11:53:46+00:00" }, { "name": "guzzlehttp/promises", @@ -1389,16 +1389,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.10.1", + "version": "2.10.3", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "73ab136360b5dfd858006eae9795e8fe43c80361" + "reference": "7c1472269227dc6f18930bd903d7a88fe6c52130" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/73ab136360b5dfd858006eae9795e8fe43c80361", - "reference": "73ab136360b5dfd858006eae9795e8fe43c80361", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7c1472269227dc6f18930bd903d7a88fe6c52130", + "reference": "7c1472269227dc6f18930bd903d7a88fe6c52130", "shasum": "" }, "require": { @@ -1486,7 +1486,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.10.1" + "source": "https://github.com/guzzle/psr7/tree/2.10.3" }, "funding": [ { @@ -1502,20 +1502,20 @@ "type": "tidelift" } ], - "time": "2026-05-20T09:27:36+00:00" + "time": "2026-05-27T11:48:20+00:00" }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.5", + "version": "v1.0.6", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" + "reference": "eef7f87bab6f204eba3c39224d8075c70c637946" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", - "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/eef7f87bab6f204eba3c39224d8075c70c637946", + "reference": "eef7f87bab6f204eba3c39224d8075c70c637946", "shasum": "" }, "require": { @@ -1524,7 +1524,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", "uri-template/tests": "1.0.0" }, "type": "library", @@ -1572,7 +1572,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.5" + "source": "https://github.com/guzzle/uri-template/tree/v1.0.6" }, "funding": [ { @@ -1588,7 +1588,7 @@ "type": "tidelift" } ], - "time": "2025-08-22T14:27:06+00:00" + "time": "2026-05-23T22:00:21+00:00" }, { "name": "intervention/gif", @@ -1803,16 +1803,16 @@ }, { "name": "laravel/framework", - "version": "v12.60.2", + "version": "v12.61.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "b8b55ce32175cc00f834a56eeb6316f18ed6ea39" + "reference": "1124062a1ca92d290c8bcb9b7f649920fa6816bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/b8b55ce32175cc00f834a56eeb6316f18ed6ea39", - "reference": "b8b55ce32175cc00f834a56eeb6316f18ed6ea39", + "url": "https://api.github.com/repos/laravel/framework/zipball/1124062a1ca92d290c8bcb9b7f649920fa6816bf", + "reference": "1124062a1ca92d290c8bcb9b7f649920fa6816bf", "shasum": "" }, "require": { @@ -2021,7 +2021,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-05-20T11:48:19+00:00" + "time": "2026-05-26T23:41:33+00:00" }, { "name": "laravel/prompts", @@ -4666,16 +4666,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.22", + "version": "v0.12.23", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "3be75d5b9244936dd4ac62ade2bfb004d13acf0f" + "reference": "4dcc0f08047d52bbde475eda481146fd8e27e1a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/3be75d5b9244936dd4ac62ade2bfb004d13acf0f", - "reference": "3be75d5b9244936dd4ac62ade2bfb004d13acf0f", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4dcc0f08047d52bbde475eda481146fd8e27e1a4", + "reference": "4dcc0f08047d52bbde475eda481146fd8e27e1a4", "shasum": "" }, "require": { @@ -4739,9 +4739,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.22" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.23" }, - "time": "2026-03-22T23:03:24+00:00" + "time": "2026-05-23T13:41:31+00:00" }, { "name": "ralouphie/getallheaders", @@ -5511,16 +5511,16 @@ }, { "name": "symfony/console", - "version": "v7.4.11", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "ed0107e43ab452aa77ae99e005b95e56b556e075" + "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/ed0107e43ab452aa77ae99e005b95e56b556e075", - "reference": "ed0107e43ab452aa77ae99e005b95e56b556e075", + "url": "https://api.github.com/repos/symfony/console/zipball/85095d2573eaefaf35e40b9513a9bf09f72cd217", + "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217", "shasum": "" }, "require": { @@ -5585,7 +5585,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.11" + "source": "https://github.com/symfony/console/tree/v7.4.13" }, "funding": [ { @@ -5605,7 +5605,7 @@ "type": "tidelift" } ], - "time": "2026-05-13T12:04:42+00:00" + "time": "2026-05-24T08:56:14+00:00" }, { "name": "symfony/css-selector", @@ -6134,16 +6134,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.4.8", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "9381209597ec66c25be154cbf2289076e64d1eab" + "reference": "bc354f47c62301e990b7874fa662326368508e2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9381209597ec66c25be154cbf2289076e64d1eab", - "reference": "9381209597ec66c25be154cbf2289076e64d1eab", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/bc354f47c62301e990b7874fa662326368508e2c", + "reference": "bc354f47c62301e990b7874fa662326368508e2c", "shasum": "" }, "require": { @@ -6192,7 +6192,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.8" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.13" }, "funding": [ { @@ -6212,20 +6212,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-24T11:20:33+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.12", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "7922b53e70d2ba2027af8bb6a59d91eb3541ea4d" + "reference": "9df847980c436451f4f51d1284491bb4356dd989" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/7922b53e70d2ba2027af8bb6a59d91eb3541ea4d", - "reference": "7922b53e70d2ba2027af8bb6a59d91eb3541ea4d", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9df847980c436451f4f51d1284491bb4356dd989", + "reference": "9df847980c436451f4f51d1284491bb4356dd989", "shasum": "" }, "require": { @@ -6311,7 +6311,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.12" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.13" }, "funding": [ { @@ -6331,7 +6331,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T09:27:11+00:00" + "time": "2026-05-27T08:31:43+00:00" }, { "name": "symfony/mailer", @@ -6419,16 +6419,16 @@ }, { "name": "symfony/mime", - "version": "v7.4.12", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "b198dd66c211c97119bcaaff7c13431dbbb5e470" + "reference": "a845722765c4f6b2ce88beaf4f4479975b186770" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b198dd66c211c97119bcaaff7c13431dbbb5e470", - "reference": "b198dd66c211c97119bcaaff7c13431dbbb5e470", + "url": "https://api.github.com/repos/symfony/mime/zipball/a845722765c4f6b2ce88beaf4f4479975b186770", + "reference": "a845722765c4f6b2ce88beaf4f4479975b186770", "shasum": "" }, "require": { @@ -6484,7 +6484,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.12" + "source": "https://github.com/symfony/mime/tree/v7.4.13" }, "funding": [ { @@ -6504,7 +6504,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-05-23T16:22:37+00:00" }, { "name": "symfony/polyfill-ctype", @@ -6591,16 +6591,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.37.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" + "reference": "e9247d281d694a5120554d9afaf54e070e88a603" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603", "shasum": "" }, "require": { @@ -6649,7 +6649,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" }, "funding": [ { @@ -6669,20 +6669,20 @@ "type": "tidelift" } ], - "time": "2026-04-26T13:13:48+00:00" + "time": "2026-05-26T05:58:03+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.37.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + "reference": "dc21118016c039a66235cf93d96b435ffb282412" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412", + "reference": "dc21118016c039a66235cf93d96b435ffb282412", "shasum": "" }, "require": { @@ -6736,7 +6736,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.38.1" }, "funding": [ { @@ -6756,20 +6756,20 @@ "type": "tidelift" } ], - "time": "2024-09-10T14:38:51+00:00" + "time": "2026-05-25T15:22:23+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.37.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { @@ -6821,7 +6821,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { @@ -6841,20 +6841,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.37.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" + "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", - "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92", + "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92", "shasum": "" }, "require": { @@ -6906,7 +6906,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1" }, "funding": [ { @@ -6926,7 +6926,7 @@ "type": "tidelift" } ], - "time": "2026-04-10T17:25:58+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { "name": "symfony/polyfill-php80", @@ -7014,16 +7014,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.37.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149" + "reference": "8339098cae28673c15cce00d80734af0453054e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/8339098cae28673c15cce00d80734af0453054e2", + "reference": "8339098cae28673c15cce00d80734af0453054e2", "shasum": "" }, "require": { @@ -7070,7 +7070,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.1" }, "funding": [ { @@ -7090,20 +7090,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T17:25:58+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { "name": "symfony/polyfill-php84", - "version": "v1.37.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06" + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/88486db2c389b290bf87ff1de7ebc1e13e42bb06", - "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", "shasum": "" }, "require": { @@ -7150,7 +7150,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" }, "funding": [ { @@ -7170,20 +7170,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T18:47:49+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { "name": "symfony/polyfill-php85", - "version": "v1.37.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee" + "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee", - "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", + "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", "shasum": "" }, "require": { @@ -7230,7 +7230,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.38.1" }, "funding": [ { @@ -7250,7 +7250,7 @@ "type": "tidelift" } ], - "time": "2026-04-26T13:10:57+00:00" + "time": "2026-05-26T02:25:22+00:00" }, { "name": "symfony/polyfill-uuid", @@ -7337,16 +7337,16 @@ }, { "name": "symfony/process", - "version": "v7.4.11", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d9593c9efa40499eb078b81144de42cbc28a31f0" + "reference": "f5804be144caceb570f6747519999636b664f24c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d9593c9efa40499eb078b81144de42cbc28a31f0", - "reference": "d9593c9efa40499eb078b81144de42cbc28a31f0", + "url": "https://api.github.com/repos/symfony/process/zipball/f5804be144caceb570f6747519999636b664f24c", + "reference": "f5804be144caceb570f6747519999636b664f24c", "shasum": "" }, "require": { @@ -7378,7 +7378,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.11" + "source": "https://github.com/symfony/process/tree/v7.4.13" }, "funding": [ { @@ -7398,20 +7398,20 @@ "type": "tidelift" } ], - "time": "2026-05-11T16:55:21+00:00" + "time": "2026-05-23T16:05:06+00:00" }, { "name": "symfony/routing", - "version": "v7.4.12", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204" + "reference": "3a162171bb008e5e0f15dce6581373a4c0e8390d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204", - "reference": "3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204", + "url": "https://api.github.com/repos/symfony/routing/zipball/3a162171bb008e5e0f15dce6581373a4c0e8390d", + "reference": "3a162171bb008e5e0f15dce6581373a4c0e8390d", "shasum": "" }, "require": { @@ -7463,7 +7463,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.12" + "source": "https://github.com/symfony/routing/tree/v7.4.13" }, "funding": [ { @@ -7483,7 +7483,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-05-24T11:20:33+00:00" }, { "name": "symfony/service-contracts", @@ -7574,16 +7574,16 @@ }, { "name": "symfony/string", - "version": "v7.4.11", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "965f7306a43383d02c6aca1e3f3bd2f0ea5dee15" + "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/965f7306a43383d02c6aca1e3f3bd2f0ea5dee15", - "reference": "965f7306a43383d02c6aca1e3f3bd2f0ea5dee15", + "url": "https://api.github.com/repos/symfony/string/zipball/961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", + "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", "shasum": "" }, "require": { @@ -7641,7 +7641,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.11" + "source": "https://github.com/symfony/string/tree/v7.4.13" }, "funding": [ { @@ -7661,7 +7661,7 @@ "type": "tidelift" } ], - "time": "2026-05-13T12:04:42+00:00" + "time": "2026-05-23T15:23:29+00:00" }, { "name": "symfony/translation", @@ -8732,16 +8732,16 @@ }, { "name": "larastan/larastan", - "version": "v3.9.6", + "version": "v3.10.0", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "9ad17e83e96b63536cb6ac39c3d40d29ff9cf636" + "reference": "2970f83398154178a739609c244577267c7ee8eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/9ad17e83e96b63536cb6ac39c3d40d29ff9cf636", - "reference": "9ad17e83e96b63536cb6ac39c3d40d29ff9cf636", + "url": "https://api.github.com/repos/larastan/larastan/zipball/2970f83398154178a739609c244577267c7ee8eb", + "reference": "2970f83398154178a739609c244577267c7ee8eb", "shasum": "" }, "require": { @@ -8755,17 +8755,17 @@ "illuminate/pipeline": "^11.44.2 || ^12.4.1 || ^13", "illuminate/support": "^11.44.2 || ^12.4.1 || ^13", "php": "^8.2", - "phpstan/phpstan": "^2.1.44" + "phpstan/phpstan": "^2.2.0" }, "require-dev": { - "doctrine/coding-standard": "^13", + "doctrine/coding-standard": "^14", "laravel/framework": "^11.44.2 || ^12.7.2 || ^13", "mockery/mockery": "^1.6.12", "nikic/php-parser": "^5.4", "orchestra/canvas": "^v9.2.2 || ^10.0.1 || ^11", "orchestra/testbench-core": "^9.12.0 || ^10.1 || ^11", "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpunit/phpunit": "^10.5.35 || ^11.5.15 || ^12.5.8" + "phpunit/phpunit": "^10.5.35 || ^11.5.15 || ^12.5.8 || ^13.1.8" }, "suggest": { "orchestra/testbench": "Using Larastan for analysing a package needs Testbench", @@ -8810,7 +8810,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.9.6" + "source": "https://github.com/larastan/larastan/tree/v3.10.0" }, "funding": [ { @@ -8818,7 +8818,7 @@ "type": "github" } ], - "time": "2026-04-16T10:02:43+00:00" + "time": "2026-05-28T08:00:58+00:00" }, { "name": "mockery/mockery", @@ -9179,11 +9179,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.55", + "version": "2.2.0", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9eaac3826ed5e9b8427350a43cac825eeca3f566", - "reference": "9eaac3826ed5e9b8427350a43cac825eeca3f566", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b4cd98348c809924f62bb9cc8c047f5e73bc9a58", + "reference": "b4cd98348c809924f62bb9cc8c047f5e73bc9a58", "shasum": "" }, "require": { @@ -9206,6 +9206,17 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ "dev", @@ -9228,7 +9239,7 @@ "type": "github" } ], - "time": "2026-05-18T11:57:34+00:00" + "time": "2026-05-28T08:22:43+00:00" }, { "name": "phpunit/php-code-coverage", From d421a191935e9689709f5a949c3d23dfd87d6ecc Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 28 May 2026 12:32:17 +0100 Subject: [PATCH 174/204] Updated translator & dependency attribution before release v26.05 --- .github/translators.txt | 9 +- dev/licensing/js-library-licenses.txt | 241 +++++++------------------ dev/licensing/php-library-licenses.txt | 2 +- 3 files changed, 75 insertions(+), 177 deletions(-) diff --git a/.github/translators.txt b/.github/translators.txt index 037887bcc36..cae26547bd3 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -533,7 +533,14 @@ JanDziaslo :: Polish Charllys Fernandes (CharllysFernandes) :: Portuguese, Brazilian Ilgiz Zigangirov (inov8) :: Russian Max Israelsson (Blezie) :: Swedish -Skiddybison5924 (chris-devel0per) :: German +Skiddybison5924 (chris-devel0per) :: German Informal; 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 diff --git a/dev/licensing/js-library-licenses.txt b/dev/licensing/js-library-licenses.txt index 1c23c093762..45d1816dd17 100644 --- a/dev/licensing/js-library-licenses.txt +++ b/dev/licensing/js-library-licenses.txt @@ -69,8 +69,9 @@ Source: zeit/arg Link: zeit/arg ----------- argparse -License: Python-2.0 +License: MIT License File: node_modules/argparse/LICENSE +Copyright: Copyright (C) 2012 by Vitaly Puzrin Source: nodeca/argparse Link: nodeca/argparse ----------- @@ -81,34 +82,6 @@ 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 @@ -169,9 +142,14 @@ 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 <******@************.***> Source: git://github.com/juliangruber/balanced-match.git -Link: https://github.com/juliangruber/balanced-match +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 ----------- binary-extensions License: MIT @@ -184,9 +162,8 @@ Link: sindresorhus/binary-extensions brace-expansion License: MIT License File: node_modules/brace-expansion/LICENSE -Copyright: Copyright (c) 2013 Julian Gruber <******@************.***> -Source: git://github.com/juliangruber/brace-expansion.git -Link: https://github.com/juliangruber/brace-expansion +Source: git+ssh://git@github.com/juliangruber/brace-expansion.git +Link: git+ssh://git@github.com/juliangruber/brace-expansion.git ----------- braces License: MIT @@ -472,8 +449,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 -Link: git://github.com/lovell/detect-libc +Source: git://github.com/lovell/detect-libc.git +Link: git://github.com/lovell/detect-libc.git ----------- detect-newline License: MIT @@ -489,12 +466,6 @@ 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 @@ -511,8 +482,8 @@ electron-to-chromium License: ISC License File: node_modules/electron-to-chromium/LICENSE Copyright: Copyright 2018 Kilian Valkhof -Source: https://github.com/kilian/electron-to-chromium/ -Link: https://github.com/kilian/electron-to-chromium/ +Source: git+https://github.com/kilian/electron-to-chromium.git +Link: git+https://github.com/kilian/electron-to-chromium.git ----------- emittery License: MIT @@ -577,13 +548,6 @@ 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 @@ -612,27 +576,6 @@ 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 @@ -814,6 +757,13 @@ 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 @@ -1026,13 +976,6 @@ 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 @@ -1529,7 +1472,7 @@ License: MIT License File: node_modules/js-yaml/LICENSE Copyright: Copyright (C) 2011-2015 by Vitaly Puzrin Source: nodeca/js-yaml -Link: nodeca/js-yaml +Link: https://github.com/nodeca/js-yaml ----------- jsdom License: MIT @@ -1645,12 +1588,6 @@ 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 @@ -1741,11 +1678,10 @@ Source: sindresorhus/mimic-fn Link: sindresorhus/mimic-fn ----------- 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 +License: BlueOak-1.0.0 +License File: node_modules/minimatch/LICENSE.md +Source: git@github.com:isaacs/minimatch +Link: git@github.com:isaacs/minimatch ----------- minimist License: MIT @@ -1754,9 +1690,8 @@ Source: git://github.com/minimistjs/minimist.git Link: https://github.com/minimistjs/minimist ----------- minipass -License: ISC -License File: node_modules/minipass/LICENSE -Copyright: Copyright (c) 2017-2023 npm, Inc., Isaac Z. Schlueter, and Contributors +License: BlueOak-1.0.0 +License File: node_modules/minipass/LICENSE.md Source: https://github.com/isaacs/minipass Link: https://github.com/isaacs/minipass ----------- @@ -1872,27 +1807,6 @@ 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 @@ -1948,13 +1862,6 @@ 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 @@ -2159,8 +2066,8 @@ resolve License: MIT License File: node_modules/resolve/LICENSE Copyright: Copyright (c) 2012 James Halliday -Source: git://github.com/browserify/resolve.git -Link: git://github.com/browserify/resolve.git +Source: ssh://github.com/browserify/resolve.git +Link: ssh://github.com/browserify/resolve.git ----------- rrweb-cssom License: MIT @@ -2405,7 +2312,7 @@ Link: sindresorhus/string-width string-width License: MIT License File: node_modules/string-width/license -Copyright: Copyright (c) Sindre Sorhus <************@*****.***> (sindresorhus.com) +Copyright: Copyright (c) Sindre Sorhus <************@*****.***> (https://sindresorhus.com) Source: sindresorhus/string-width Link: sindresorhus/string-width ----------- @@ -2570,13 +2477,6 @@ 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 @@ -2810,7 +2710,7 @@ Link: chalk/wrap-ansi wrap-ansi License: MIT License File: node_modules/wrap-ansi/license -Copyright: Copyright (c) Sindre Sorhus <************@*****.***> (sindresorhus.com) +Copyright: Copyright (c) Sindre Sorhus <************@*****.***> (https://sindresorhus.com) Source: chalk/wrap-ansi Link: chalk/wrap-ansi ----------- @@ -2892,12 +2792,6 @@ 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 @@ -3172,15 +3066,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: https://github.com/codemirror/autocomplete.git -Link: https://github.com/codemirror/autocomplete.git +Source: git+https://github.com/codemirror/autocomplete.git +Link: git+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: https://github.com/codemirror/commands.git -Link: https://github.com/codemirror/commands.git +Source: git+https://github.com/codemirror/commands.git +Link: git+https://github.com/codemirror/commands.git ----------- @codemirror/lang-css License: MIT @@ -3200,8 +3094,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: https://github.com/codemirror/lang-javascript.git -Link: https://github.com/codemirror/lang-javascript.git +Source: git+https://github.com/codemirror/lang-javascript.git +Link: git+https://github.com/codemirror/lang-javascript.git ----------- @codemirror/lang-json License: MIT @@ -3235,8 +3129,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: https://github.com/codemirror/language.git -Link: https://github.com/codemirror/language.git +Source: git+https://github.com/codemirror/language.git +Link: git+https://github.com/codemirror/language.git ----------- @codemirror/legacy-modes License: MIT @@ -3249,22 +3143,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: https://github.com/codemirror/lint.git -Link: https://github.com/codemirror/lint.git +Source: git+https://github.com/codemirror/lint.git +Link: git+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: https://github.com/codemirror/search.git -Link: https://github.com/codemirror/search.git +Source: git+https://github.com/codemirror/search.git +Link: git+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: https://github.com/codemirror/state.git -Link: https://github.com/codemirror/state.git +Source: git+https://github.com/codemirror/state.git +Link: git+https://github.com/codemirror/state.git ----------- @codemirror/theme-one-dark License: MIT @@ -3277,8 +3171,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: https://github.com/codemirror/view.git -Link: https://github.com/codemirror/view.git +Source: git+https://github.com/codemirror/view.git +Link: git+https://github.com/codemirror/view.git ----------- @cspotcode/source-map-support License: MIT @@ -3358,12 +3252,6 @@ 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 @@ -3567,6 +3455,13 @@ 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 @@ -3704,18 +3599,12 @@ 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 -Source: https://github.com/sinclairzx81/typebox -Link: https://github.com/sinclairzx81/typebox +Copyright: Copyright (c) 2017-2026 Haydn Paterson +Source: https://github.com/sinclairzx81/typebox-legacy +Link: https://github.com/sinclairzx81/typebox-legacy ----------- @sinonjs/commons License: BSD-3-Clause @@ -3798,6 +3687,13 @@ 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 @@ -3847,11 +3743,6 @@ 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 diff --git a/dev/licensing/php-library-licenses.txt b/dev/licensing/php-library-licenses.txt index 348fce2ff98..2259c44c301 100644 --- a/dev/licensing/php-library-licenses.txt +++ b/dev/licensing/php-library-licenses.txt @@ -109,7 +109,7 @@ firebase/php-jwt License: BSD-3-Clause License File: vendor/firebase/php-jwt/LICENSE Copyright: Copyright (c) 2011, Neuman Vong -Source: https://github.com/firebase/php-jwt.git +Source: https://github.com/googleapis/php-jwt.git Link: https://github.com/firebase/php-jwt ----------- fruitcake/php-cors From f01bb749ab3f11fc465bf5f247dc84e297e4f98a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 30 May 2026 13:45:25 +0100 Subject: [PATCH 175/204] Workflows: Attempted fixing crowdin files Currently causing extra files to be created alongside previous files in crowdin --- .forgejo/workflows/sync-translations.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.forgejo/workflows/sync-translations.yml b/.forgejo/workflows/sync-translations.yml index a0c09ba8f6b..9501c15e133 100644 --- a/.forgejo/workflows/sync-translations.yml +++ b/.forgejo/workflows/sync-translations.yml @@ -22,6 +22,7 @@ jobs: - 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 From 37f2d05118fa98ee9cb68b8a51c6ae90612de813 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 6 Jun 2026 10:37:06 +0100 Subject: [PATCH 176/204] Search: Prevented tag search using unusable numbers These would trigger an error on use, and could be abused to fill logs. Added test to cover. Thanks to Stephen O. / Sakusen for reporting. --- app/Search/SearchRunner.php | 2 +- tests/Search/EntitySearchTest.php | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/Search/SearchRunner.php b/app/Search/SearchRunner.php index 3912541723f..d49cb439a6b 100644 --- a/app/Search/SearchRunner.php +++ b/app/Search/SearchRunner.php @@ -290,7 +290,7 @@ protected function applyTagSearch(EloquentBuilder $query, TagSearchOption $optio $query->where('name', '=', $tagParts['name']); } - if (is_numeric($tagParts['value']) && $tagParts['operator'] !== 'like') { + if (is_numeric($tagParts['value']) && is_finite($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. diff --git a/tests/Search/EntitySearchTest.php b/tests/Search/EntitySearchTest.php index fc300241bcf..d29c44eda3d 100644 --- a/tests/Search/EntitySearchTest.php +++ b/tests/Search/EntitySearchTest.php @@ -233,6 +233,18 @@ public function test_search_filters() $this->get('/search?term=' . urlencode('danzorbhsing {created_before:2037-01-01}'))->assertDontSee($page->name); } + public function test_search_tags_with_unexpected_numeric_values_does_not_cause_error() + { + $pageA = $this->entities->page(); + $pageA->name = 'MyTestPageWithAwkwardNumericTagValue'; + $pageA->save(); + $pageA->tags()->save(new Tag(['name' => 'Count', 'value' => '1E999'])); + + $resp = $this->asEditor()->get('/search?term=' . urlencode('[Count=1E999]')); + $resp->assertStatus(200); + $resp->assertSee('MyTestPageWithAwkwardNumericTagValue'); + } + public function test_entity_selector_search() { $page = $this->entities->newPage(['name' => 'my ajax search test', 'html' => 'ajax test']); From 81f77a95de4b060fa77a5376198a80a24cfcca59 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 6 Jun 2026 15:21:15 +0100 Subject: [PATCH 177/204] Content Filtering: Limited file protocol to just anchor hrefs Updated allow list/purifier system to only allow file protocol use on anchor hrefs to avoid potential security concerns with, after export, content being auto loaded via interactive elements like embeds/objects/videos etc... Updated tests to cover. Thanks to Gurmandeep Deol at Seneca Polytechnic for reporting. --- app/Util/HtmlContentFilter.php | 1 + .../ConfiguredHtmlPurifier.php | 19 ++++++- .../Filters/UriLimitFileProtocolToAnchors.php | 55 +++++++++++++++++++ tests/Entity/PageContentFilteringTest.php | 7 +++ 4 files changed, 79 insertions(+), 3 deletions(-) rename app/Util/{ => HtmlPurifier}/ConfiguredHtmlPurifier.php (86%) create mode 100644 app/Util/HtmlPurifier/Filters/UriLimitFileProtocolToAnchors.php diff --git a/app/Util/HtmlContentFilter.php b/app/Util/HtmlContentFilter.php index 2ef797d1304..26f22e6ca6a 100644 --- a/app/Util/HtmlContentFilter.php +++ b/app/Util/HtmlContentFilter.php @@ -2,6 +2,7 @@ namespace BookStack\Util; +use BookStack\Util\HtmlPurifier\ConfiguredHtmlPurifier; use DOMAttr; use DOMElement; use DOMNodeList; diff --git a/app/Util/ConfiguredHtmlPurifier.php b/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php similarity index 86% rename from app/Util/ConfiguredHtmlPurifier.php rename to app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php index 1f2528e7155..307cdb74f28 100644 --- a/app/Util/ConfiguredHtmlPurifier.php +++ b/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php @@ -1,13 +1,15 @@ getDefinition('HTML', true, true); if ($htmlDef instanceof HTMLPurifier_HTMLDefinition) { - $this->configureDefinition($htmlDef); + $this->configureHtmlDefinition($htmlDef); + } + + /** @var \HTMLPurifier_URIDefinition $uriDef */ + $uriDef = $config->getDefinition('URI', true, true); + if ($uriDef instanceof HTMLPurifier_URIDefinition) { + $this->configureUriDefinition($uriDef); } $this->purifier = new HTMLPurifier($config); @@ -91,7 +99,7 @@ protected function setConfig(HTMLPurifier_Config $config, string $cachePath): vo // $config->set('Cache.DefinitionImpl', null); // Disable cache during testing } - public function configureDefinition(HTMLPurifier_HTMLDefinition $definition): void + protected function configureHtmlDefinition(HTMLPurifier_HTMLDefinition $definition): void { // Allow the object element $definition->addElement( @@ -151,6 +159,11 @@ public function configureDefinition(HTMLPurifier_HTMLDefinition $definition): vo $definition->addAttribute('a', 'data-mention-user-id', 'Number'); } + protected function configureUriDefinition(HTMLPurifier_URIDefinition $definition): void + { + $definition->registerFilter(new UriLimitFileProtocolToAnchors()); + } + public function purify(string $html): string { return $this->purifier->purify($html); diff --git a/app/Util/HtmlPurifier/Filters/UriLimitFileProtocolToAnchors.php b/app/Util/HtmlPurifier/Filters/UriLimitFileProtocolToAnchors.php new file mode 100644 index 00000000000..19ca9cc82b9 --- /dev/null +++ b/app/Util/HtmlPurifier/Filters/UriLimitFileProtocolToAnchors.php @@ -0,0 +1,55 @@ +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; + } +} + +// vim: et sw=4 sts=4 diff --git a/tests/Entity/PageContentFilteringTest.php b/tests/Entity/PageContentFilteringTest.php index 449189a898c..0ebbd9a0eff 100644 --- a/tests/Entity/PageContentFilteringTest.php +++ b/tests/Entity/PageContentFilteringTest.php @@ -464,6 +464,11 @@ public function test_allow_list_style_filtering() '
    Hello!
    ' => '
    Hello!
    ', '
    Hello!
    ' => '
    Hello!
    ', '
    Hello!
    ' => '
    Hello!
    ', + '' => '', + '' => '', + '' => '', + '
    My local image
    ' => '
    ', + '
    My local image
    ' => '
    ', ]; config()->set('app.content_filtering', 'a'); @@ -476,6 +481,7 @@ public function test_allow_list_style_filtering() $resp = $this->get($page->getUrl()); $resp->assertSee($expected, false); + $resp->assertDontSee($input, false); } } @@ -484,6 +490,7 @@ public function test_allow_list_does_not_filter_cases() $testCasesExpectedByInput = [ '

    New tab linkydoodle

    ', '

    @mentionusertext

    ', + '

    Link to file

    ', '
    Hello

    Mydetailshere

    ', ]; From b7325fdf0efde6f863beed67cde488641a05ef83 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 7 Jun 2026 10:22:52 +0100 Subject: [PATCH 178/204] Attachments: Moved perm checks before validation Avoids providing responses with potential sensitive attachment info before permission checks. Added tests to cover. Thanks to Rafael Castilho for reporting. --- .../Controllers/AttachmentController.php | 16 ++++------ tests/Uploads/AttachmentTest.php | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/app/Uploads/Controllers/AttachmentController.php b/app/Uploads/Controllers/AttachmentController.php index 12f49dffee5..bdd96d6ad7b 100644 --- a/app/Uploads/Controllers/AttachmentController.php +++ b/app/Uploads/Controllers/AttachmentController.php @@ -107,6 +107,9 @@ public function update(Request $request, string $attachmentId) { /** @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, [ @@ -120,10 +123,6 @@ public function update(Request $request, string $attachmentId) ]), 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,6 +141,10 @@ public function update(Request $request, string $attachmentId) 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, [ @@ -156,11 +159,6 @@ public function attachLink(Request $request) ]), 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)); diff --git a/tests/Uploads/AttachmentTest.php b/tests/Uploads/AttachmentTest.php index 2d402c34006..22e4f4610a0 100644 --- a/tests/Uploads/AttachmentTest.php +++ b/tests/Uploads/AttachmentTest.php @@ -159,6 +159,38 @@ public function test_attachment_updating() $this->files->deleteAllAttachmentFiles(); } + public function test_attachment_update_without_permission() + { + $page = $this->entities->page(); + $attachment = Attachment::factory()->create(['uploaded_to' => $page->id]); + + $this->permissions->disableEntityInheritedPermissions($page); + + $resp = $this->asViewer()->put("attachments/{$attachment->id}", [ + 'attachment_edit_name' => 'My new attachment name', + 'attachment_edit_url' => 'https://test.example.com', + ]); + + $this->assertPermissionError($resp); + } + + public function test_attachment_update_without_permission_with_validation_errors() + { + $page = $this->entities->page(); + /** @var Attachment $attachment */ + $attachment = Attachment::factory()->create(['uploaded_to' => $page->id]); + + $this->permissions->disableEntityInheritedPermissions($page); + + $resp = $this->asViewer()->put("attachments/{$attachment->id}", [ + 'attachment_edit_name' => '', + 'attachment_edit_url' => 'https://test.example.com', + ]); + + $this->assertPermissionError($resp); + $resp->assertDontSee($attachment->path); + } + public function test_file_deletion() { $page = $this->entities->page(); From 84a23fb23f8d15935521ad4f3fdc335be6d90744 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 7 Jun 2026 10:45:40 +0100 Subject: [PATCH 179/204] Logs: Prevented NotifyExceptions for reporting to error logs This is to reduce the amount of content which will be logged, since these messages don't really indicate an actual system error but advise the user of something which went wrong with their request. --- app/Exceptions/Handler.php | 1 + app/Exceptions/NotifyException.php | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 08d326ad81c..97a2b0a4f24 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -25,6 +25,7 @@ class Handler extends ExceptionHandler protected $dontReport = [ NotFoundException::class, StoppedAuthenticationException::class, + NotifyException::class, ]; /** diff --git a/app/Exceptions/NotifyException.php b/app/Exceptions/NotifyException.php index b62b8fde646..d2fba30c4e9 100644 --- a/app/Exceptions/NotifyException.php +++ b/app/Exceptions/NotifyException.php @@ -6,18 +6,22 @@ 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 $message; - public string $redirectLocation; - protected int $status; - - public function __construct(string $message, string $redirectLocation = '/', int $status = 500) - { + public function __construct( + string $message, + public string $redirectLocation = '/', + protected int $status = 500 + ) { $this->message = $message; - $this->redirectLocation = $redirectLocation; - $this->status = $status; - parent::__construct(); } From 9fc46f76f687581e2ee9a54d097320214c0656cf Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 8 Jun 2026 04:30:58 +0000 Subject: [PATCH 180/204] New Crowdin translations by GitHub Action --- lang/cs/activities.php | 4 ++-- lang/cs/auth.php | 2 +- lang/cs/entities.php | 8 ++++---- lang/cs/settings.php | 10 +++++----- lang/da/auth.php | 2 +- lang/da/entities.php | 6 +++--- lang/da/settings.php | 2 +- lang/de/auth.php | 2 +- lang/de/entities.php | 8 ++++---- lang/de/settings.php | 4 ++-- lang/de_informal/auth.php | 2 +- lang/de_informal/entities.php | 8 ++++---- lang/de_informal/settings.php | 6 +++--- lang/hu/settings.php | 2 +- lang/ja/entities.php | 2 +- lang/ja/settings.php | 2 +- lang/nl/entities.php | 2 +- lang/pt/entities.php | 4 ++-- lang/pt/errors.php | 4 ++-- lang/uk/activities.php | 4 ++-- lang/uk/auth.php | 2 +- lang/uk/entities.php | 8 ++++---- lang/uk/errors.php | 2 +- lang/uk/settings.php | 10 +++++----- 24 files changed, 53 insertions(+), 53 deletions(-) diff --git a/lang/cs/activities.php b/lang/cs/activities.php index 3e497ecedbe..65840f9376a 100644 --- a/lang/cs/activities.php +++ b/lang/cs/activities.php @@ -99,8 +99,8 @@ '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' => 'reset MFA for user', - 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', + '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', diff --git a/lang/cs/auth.php b/lang/cs/auth.php index 4ad62ff1ae6..25a9e48855a 100644 --- a/lang/cs/auth.php +++ b/lang/cs/auth.php @@ -8,7 +8,7 @@ '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' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + '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', diff --git a/lang/cs/entities.php b/lang/cs/entities.php index e44dcf84e62..9d8a76e9a0e 100644 --- a/lang/cs/entities.php +++ b/lang/cs/entities.php @@ -173,7 +173,7 @@ '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' => 'Auto sort option rules can be created in the "Lists & Sorting" settings area by a user with the relevant permissions.', + '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 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Skrýt/Zobrazit postranní panel', - '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_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', diff --git a/lang/cs/settings.php b/lang/cs/settings.php index c11b7ee586e..85c479dbddb 100644 --- a/lang/cs/settings.php +++ b/lang/cs/settings.php @@ -207,7 +207,7 @@ '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' => 'Controlled by page delete permissions', + '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 +260,13 @@ 'users_api_tokens_create' => 'Vytvořit Token', 'users_api_tokens_expires' => 'Vyprší', 'users_api_tokens_docs' => 'Dokumentace API', - 'users_mfa' => 'Vícefázové ověření', + 'users_mfa' => 'Vícefaktorové 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' => '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?', + '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', diff --git a/lang/da/auth.php b/lang/da/auth.php index 95ebd6230e6..471be2a114a 100644 --- a/lang/da/auth.php +++ b/lang/da/auth.php @@ -8,7 +8,7 @@ 'failed' => 'De indtastede brugeroplysninger stemmer ikke overens med vores registreringer.', 'throttle' => 'For mange mislykkede loginforsøg. Prøv igen om :seconds sekunder.', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'For mange mislykkede loginforsøg. Prøv igen om :seconds sekunder.', // Login & Register 'sign_up' => 'Registrer', diff --git a/lang/da/entities.php b/lang/da/entities.php index f1c5735eddf..a078ad9d85f 100644 --- a/lang/da/entities.php +++ b/lang/da/entities.php @@ -331,9 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Sidebjælke til/fra', - '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_contents' => 'Sideindhold', + 'page_contents_none' => 'Ingen overskrifter blev fundet i sidens indhold.', + 'page_contents_info' => 'Indholdsmenuen er genereret fra alle kursformater, der bruges på siden.', 'page_tags' => 'Sidetags', 'chapter_tags' => 'Kapiteltags', 'book_tags' => 'Bogtags', diff --git a/lang/da/settings.php b/lang/da/settings.php index 448b00c4c07..9d83f5004db 100644 --- a/lang/da/settings.php +++ b/lang/da/settings.php @@ -367,7 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', - 'th' => 'ภาษาไทย', + 'th' => 'Thailandsk', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/de/auth.php b/lang/de/auth.php index 0ae2dcd9799..22205af60b2 100644 --- a/lang/de/auth.php +++ b/lang/de/auth.php @@ -8,7 +8,7 @@ 'failed' => 'Diese Anmeldedaten stimmen nicht mit unseren Aufzeichnungen überein.', 'throttle' => 'Zu viele Anmeldeversuche. Bitte versuchen Sie es in :seconds Sekunden erneut.', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Zu viele Multi-Faktor-Verifizierungsversuche. Bitte versuchen Sie es in :seconds Sekunden erneut.', // Login & Register 'sign_up' => 'Registrieren', diff --git a/lang/de/entities.php b/lang/de/entities.php index a35a5f4c465..7fa7c43bcd4 100644 --- a/lang/de/entities.php +++ b/lang/de/entities.php @@ -173,7 +173,7 @@ 'books_sort_desc' => 'Verschieben Sie Kapitel und Seiten innerhalb eines Buches, um dessen Inhalt neu zu ordnen. Es können weitere Bücher hinzugefügt werden, wodurch Kapitel und Seiten problemlos zwischen den Büchern verschoben werden können. Optional kann eine automatische Sortierregel festgelegt werden, um den Inhalt dieses Buches bei Änderungen automatisch zu sortieren.', 'books_sort_auto_sort' => 'Automatische Sortierfunktionsoption', 'books_sort_auto_sort_active' => 'Automatische Sortierung aktiv: :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_auto_sort_creation_hint' => 'Regeln für die automatische Sortierung können von einem Benutzer mit den entsprechenden Berechtigungen im Einstellungsbereich "Listen & Sortieren" erstellt werden.', 'books_sort_named' => 'Buch ":bookName" sortieren', 'books_sort_name' => 'Sortieren nach Namen', 'books_sort_created' => 'Sortieren nach Erstellungsdatum', @@ -331,9 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Seitenleiste umschalten', - '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_contents' => 'Seiteninhalt', + 'page_contents_none' => 'Es wurden keine Überschriften im Seiteninhalt gefunden.', + 'page_contents_info' => 'Das Inhaltsmenü wird aus allen auf der Seite verwendeten Überschriften generiert.', 'page_tags' => 'Seiten-Schlagwörter', 'chapter_tags' => 'Kapitel-Schlagwörter', 'book_tags' => 'Buch-Schlagwörter', diff --git a/lang/de/settings.php b/lang/de/settings.php index 983de75baf6..29e857098d6 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -207,7 +207,7 @@ 'role_all' => 'Alle', 'role_own' => 'Eigene', 'role_controlled_by_asset' => 'Abhängig von dem Asset, in das sie hochgeladen werden', - 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', + 'role_controlled_by_page_delete' => 'Kontrolliert durch die Berechtigung zum Löschen einer Seite', 'role_save' => 'Rolle speichern', 'role_users' => 'Dieser Rolle zugeordnete Benutzer', 'role_users_none' => 'Derzeit sind diesem Rollentyp keine Benutzer zugewiesen', @@ -265,7 +265,7 @@ 'users_mfa_x_methods' => ':count Methode konfiguriert|:count Methoden konfiguriert', 'users_mfa_configure' => 'Methoden konfigurieren', 'users_mfa_reset' => 'Setze Multifaktor-Authentifizierung zurück', - 'users_mfa_reset_desc' => 'Dies wird alle konfigurierten Multifaktor-Authentifizierungsmethoden für diesen Nutzer zurücksetzen. Falls Multifaktor-Authentifizierung für eine seiner Rollen erforderlich ist, werden sie aufgefordert, neue Methoden beim nächsten Login zu konfigurieren.', + 'users_mfa_reset_desc' => 'Dies wird alle konfigurierten Multifaktor-Authentifizierungsmethoden für diesen Nutzer zurücksetzen. Falls Multifaktor-Authentifizierung für eine seiner Rollen erforderlich ist, wird der Nutzer aufgefordert, neue Methoden beim nächsten Login zu konfigurieren.', 'users_mfa_reset_confirm' => 'Sind Sie sicher, dass Sie diese Multi-Faktor-Authentifizierungsmethode für diesen Nutzer zurücksetzen möchten?', // API Tokens diff --git a/lang/de_informal/auth.php b/lang/de_informal/auth.php index b91db255170..2414f1aa7fe 100644 --- a/lang/de_informal/auth.php +++ b/lang/de_informal/auth.php @@ -8,7 +8,7 @@ 'failed' => 'Die eingegebenen Anmeldedaten sind ungültig.', 'throttle' => 'Zu viele Anmeldeversuche. Bitte versuche es in :seconds Sekunden erneut.', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Zu viele Multi-Faktor-Verifizierungsversuche. Bitte versuche es in :seconds Sekunden erneut.', // Login & Register 'sign_up' => 'Registrieren', diff --git a/lang/de_informal/entities.php b/lang/de_informal/entities.php index 4b4c7e4ef5f..78c1e3319c6 100644 --- a/lang/de_informal/entities.php +++ b/lang/de_informal/entities.php @@ -173,7 +173,7 @@ 'books_sort_desc' => 'Kapitel und Seiten innerhalb eines Buches verschieben, um dessen Inhalt zu reorganisieren. Andere Bücher können hinzugefügt werden, was das Verschieben von Kapiteln und Seiten zwischen Büchern erleichtert. Optional kann eine automatische Sortierregel erstellt werden, um den Inhalt dieses Buches nach Änderungen automatisch zu sortieren.', 'books_sort_auto_sort' => 'Auto-Sortieroption', 'books_sort_auto_sort_active' => 'Automatische Sortierung aktiv: :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_auto_sort_creation_hint' => 'Regeln für die automatische Sortierung können von einem Benutzer mit den entsprechenden Berechtigungen im Einstellungsbereich "Listen & Sortieren" erstellt werden.', 'books_sort_named' => 'Buch ":bookName" sortieren', 'books_sort_name' => 'Sortieren nach Namen', 'books_sort_created' => 'Sortieren nach Erstellungsdatum', @@ -331,9 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Seitenleiste umschalten', - '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_contents' => 'Seiteninhalt', + 'page_contents_none' => 'Es wurden keine Überschriften im Seiteninhalt gefunden.', + 'page_contents_info' => 'Das Inhaltsmenü wird aus allen auf der Seite verwendeten Überschriften generiert.', 'page_tags' => 'Seiten-Schlagwörter', 'chapter_tags' => 'Kapitel-Schlagwörter', 'book_tags' => 'Buch-Schlagwörter', diff --git a/lang/de_informal/settings.php b/lang/de_informal/settings.php index 84e0c8b1042..7c1796afcc6 100644 --- a/lang/de_informal/settings.php +++ b/lang/de_informal/settings.php @@ -208,7 +208,7 @@ 'role_all' => 'Alle', 'role_own' => 'Eigene', 'role_controlled_by_asset' => 'Berechtigungen werden vom Uploadziel bestimmt', - 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', + 'role_controlled_by_page_delete' => 'Kontrolliert durch die Berechtigung zum Löschen einer Seite', 'role_save' => 'Rolle speichern', 'role_users' => 'Dieser Rolle zugeordnete Benutzer', 'role_users_none' => 'Bisher sind dieser Rolle keine Benutzer zugeordnet', @@ -266,8 +266,8 @@ 'users_mfa_x_methods' => ':count Methode konfiguriert|:count Methoden konfiguriert', 'users_mfa_configure' => 'Methoden konfigurieren', 'users_mfa_reset' => 'Setze Multifaktor-Authentifizierung zurück', - 'users_mfa_reset_desc' => 'Dies wird alle konfigurierten Multifaktor-Authentifizierungsmethoden für diesen Nutzer zurücksetzen. Falls Multifaktor-Authentifizierung für eine seiner Rollen erforderlich ist, werden sie aufgefordert, neue Methoden beim nächsten Login zu konfigurieren.', - 'users_mfa_reset_confirm' => 'Sind Sie sicher, dass Sie diese Multi-Faktor-Authentifizierungsmethode für diesen Nutzer zurücksetzen möchten?', + 'users_mfa_reset_desc' => 'Dies wird alle konfigurierten Multifaktor-Authentifizierungsmethoden für diesen Nutzer zurücksetzen. Falls Multifaktor-Authentifizierung für eine seiner Rollen erforderlich ist, wird der Nutzer aufgefordert, neue Methoden beim nächsten Login zu konfigurieren.', + 'users_mfa_reset_confirm' => 'Bist du sicher, dass du diese Multi-Faktor-Authentifizierungsmethode für diesen Nutzer zurücksetzen möchtest?', // API Tokens 'user_api_token_create' => 'Neuen API-Token erstellen', diff --git a/lang/hu/settings.php b/lang/hu/settings.php index 6dd7de33b54..3aaa91c35b7 100644 --- a/lang/hu/settings.php +++ b/lang/hu/settings.php @@ -367,7 +367,7 @@ 'sk' => 'Slovensky', 'sl' => 'Slovenščina', 'sv' => 'Svenska', - 'th' => 'ภาษาไทย', + 'th' => 'Thai', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/ja/entities.php b/lang/ja/entities.php index eaaddf5b3af..66019fa1aca 100644 --- a/lang/ja/entities.php +++ b/lang/ja/entities.php @@ -173,7 +173,7 @@ '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_auto_sort_creation_hint' => '自動ソートオプションのルールは、関連する権限を持ったユーザーによって設定の「一覧とソート」エリアで作成できます。', 'books_sort_named' => 'ブック「:bookName」を並べ替え', 'books_sort_name' => '名前で並べ替え', 'books_sort_created' => '作成日で並べ替え', diff --git a/lang/ja/settings.php b/lang/ja/settings.php index 95f33f34004..bca5ed0049f 100644 --- a/lang/ja/settings.php +++ b/lang/ja/settings.php @@ -207,7 +207,7 @@ 'role_all' => '全て', 'role_own' => '自身', 'role_controlled_by_asset' => 'このアセットに対し、右記の操作を許可:', - 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', + 'role_controlled_by_page_delete' => 'ページ削除権限を適用', 'role_save' => '役割を保存', 'role_users' => 'この役割を持つユーザー', 'role_users_none' => 'この役割が付与されたユーザーはいません', diff --git a/lang/nl/entities.php b/lang/nl/entities.php index e90aff57573..3695277a71e 100644 --- a/lang/nl/entities.php +++ b/lang/nl/entities.php @@ -331,7 +331,7 @@ // Editor Sidebar 'toggle_sidebar' => 'Zijbalk Tonen/Verbergen', - 'page_contents' => 'Page Contents', + 'page_contents' => 'Pagina Inhoud', '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' => 'Pagina Labels', diff --git a/lang/pt/entities.php b/lang/pt/entities.php index 3bdaeb12c8d..c278796a43f 100644 --- a/lang/pt/entities.php +++ b/lang/pt/entities.php @@ -50,8 +50,8 @@ 'import_zip_validation_errors' => 'Errors were detected while validating the provided ZIP file:', 'import_pending' => 'Pending Imports', 'import_pending_none' => 'No imports have been started.', - 'import_continue' => 'Continue Import', - 'import_continue_desc' => 'Review the content due to be imported from the uploaded ZIP file. When ready, run the import to add its contents to this system. The uploaded ZIP import file will be automatically removed on successful import.', + 'import_continue' => 'Continuar importação', + 'import_continue_desc' => 'Continuar importação', 'import_details' => 'Import Details', 'import_run' => 'Run Import', 'import_size' => ':size Import ZIP Size', diff --git a/lang/pt/errors.php b/lang/pt/errors.php index 522d7f4c81a..e32257e6240 100644 --- a/lang/pt/errors.php +++ b/lang/pt/errors.php @@ -78,7 +78,7 @@ // Users 'users_cannot_delete_only_admin' => 'Não pode excluir o único administrador', 'users_cannot_delete_guest' => 'Não pode excluir o usuário convidado', - 'users_could_not_send_invite' => 'Could not create user since invite email failed to send', + 'users_could_not_send_invite' => 'Não foi possível criar o utilizador, pois o envio do endereço eletrónico de convite falhou', // Roles 'role_cannot_be_edited' => 'Este cargo não pode ser editado', @@ -106,7 +106,7 @@ 'back_soon' => 'Voltaremos em breve.', // Import - 'import_zip_cant_read' => 'Could not read ZIP file.', + 'import_zip_cant_read' => 'Não foi possível ler o ficheiro ZIP.', 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', diff --git a/lang/uk/activities.php b/lang/uk/activities.php index 140fec47c7e..21ebcf970d9 100644 --- a/lang/uk/activities.php +++ b/lang/uk/activities.php @@ -99,8 +99,8 @@ 'user_update_notification' => 'Користувача було успішно оновлено', 'user_delete' => 'вилучений користувач', 'user_delete_notification' => 'Користувача успішно видалено', - 'user_mfa_reset' => 'reset MFA for user', - 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', + 'user_mfa_reset' => 'скинути MFA для користувача', + 'user_mfa_reset_notification' => 'Скидання методів багатофакторної автентифікації', // API Tokens 'api_token_create' => 'створений APi токен', diff --git a/lang/uk/auth.php b/lang/uk/auth.php index cb927cfd47f..9915fa9433a 100644 --- a/lang/uk/auth.php +++ b/lang/uk/auth.php @@ -8,7 +8,7 @@ 'failed' => 'Цей обліковий запис не знайдено.', 'throttle' => 'Забагато спроб входу в систему. Будь ласка, спробуйте ще раз через :seconds секунд.', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Занадто багато спроб багатофакторної перевірки. Будь ласка, спробуйте ще раз через :seconds секунд.', // Login & Register 'sign_up' => 'Реєстрація', diff --git a/lang/uk/entities.php b/lang/uk/entities.php index a77b8e77d13..9e775c88bad 100644 --- a/lang/uk/entities.php +++ b/lang/uk/entities.php @@ -173,7 +173,7 @@ '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_auto_sort_creation_hint' => 'Правила автоматичного сортування можуть бути створені в області налаштувань "Списки і сортування" за допомогою користувача з відповідними дозволами.', 'books_sort_named' => 'Сортувати книгу :bookName', 'books_sort_name' => 'Сортувати за назвою', 'books_sort_created' => 'Сортувати за датою створення', @@ -331,9 +331,9 @@ // 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_contents' => 'Вміст сторінки', + 'page_contents_none' => 'У тексті сторінки не знайдено заголовків.', + 'page_contents_info' => 'Вміст меню створюється з будь-яких форматів заголовків, використовуваних для сторінки.', 'page_tags' => 'Теги сторінки', 'chapter_tags' => 'Теги розділів', 'book_tags' => 'Теги книг', diff --git a/lang/uk/errors.php b/lang/uk/errors.php index e7632359310..8e332bdedfb 100644 --- a/lang/uk/errors.php +++ b/lang/uk/errors.php @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => 'Секрет, наданий для даного використовуваного токена API є неправильним', 'api_user_no_api_permission' => 'Власник використовуваного токена API не має дозволу здійснювати виклики API', 'api_user_token_expired' => 'Термін дії токена авторизації закінчився', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Дозволяються тільки запити GET при використанні API з автентифікацією на основі cookie', // Settings & Maintenance 'maintenance_test_email_failure' => 'Помилка під час надсилання тестового електронного листа:', diff --git a/lang/uk/settings.php b/lang/uk/settings.php index 02fd6a4f6ee..521dc9578fd 100644 --- a/lang/uk/settings.php +++ b/lang/uk/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Спочатку розділи', 'sort_rule_op_chapters_last' => 'Розділи останні', 'sorting_page_limits' => 'Обмеження відображення сторінок', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sorting_page_limits_desc' => 'Кількість елементів для відображення в різних списках в системі. Зазвичай менша кількість буде більш продуктивною, в той час як більша кількість уникає необхідність натискання на кілька сторінок. Використання кратного 6 рекомендується.', // Maintenance settings 'maint' => 'Обслуговування', @@ -207,7 +207,7 @@ 'role_all' => 'Все', 'role_own' => 'Власне', 'role_controlled_by_asset' => 'Контролюється за об\'єктом, до якого вони завантажуються', - 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', + 'role_controlled_by_page_delete' => 'Керується правами доступу для видалення сторінки', 'role_save' => 'Зберегти роль', 'role_users' => 'Користувачі в цій ролі', 'role_users_none' => 'Наразі жоден користувач не призначений для цієї ролі', @@ -264,9 +264,9 @@ '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?', + 'users_mfa_reset' => 'Скинути методи багатофакторної автентифікації', + 'users_mfa_reset_desc' => 'Це скине та очистить всі налаштовані методи багатофакторної аутентифікації для цього користувача. Якщо багатофакторна аутентифікація обов\'язкова будь-якою зі своїх ролей, то вона буде запитана налаштувати нові способи при наступному вході в систему.', + 'users_mfa_reset_confirm' => 'Ви впевнені, що хочете скинути багатофакторну аутентифікацію для цього користувача?', // API Tokens 'user_api_token_create' => 'Створити токен API', From cc0b059fa496e265839733ed30a1ba121274dfab Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 9 Jun 2026 12:47:28 +0100 Subject: [PATCH 181/204] Deps: Updated PHP package versions --- composer.lock | 118 ++++++++++++++++++++++++++------------------------ 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/composer.lock b/composer.lock index 710067eb0a6..e0726839914 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.382.2", + "version": "3.384.5", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "6844cc6421c47d6b96633ab8039045012acbeb27" + "reference": "c7d34f2d60515bd0c307e462268f75877842da4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6844cc6421c47d6b96633ab8039045012acbeb27", - "reference": "6844cc6421c47d6b96633ab8039045012acbeb27", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c7d34f2d60515bd0c307e462268f75877842da4a", + "reference": "c7d34f2d60515bd0c307e462268f75877842da4a", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.382.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.384.5" }, - "time": "2026-05-27T18:11:41+00:00" + "time": "2026-06-08T18:25:02+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1179,25 +1179,26 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.10.5", + "version": "7.11.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "7c8d84b39e680315f687e8662a9d6fb0865c5148" + "reference": "5af96f374e0ab4ebd747b8310888c99d3adb0a8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7c8d84b39e680315f687e8662a9d6fb0865c5148", - "reference": "7c8d84b39e680315f687e8662a9d6fb0865c5148", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/5af96f374e0ab4ebd747b8310888c99d3adb0a8c", + "reference": "5af96f374e0ab4ebd747b8310888c99d3adb0a8c", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^2.3", - "guzzlehttp/psr7": "^2.8", + "guzzlehttp/promises": "^2.5", + "guzzlehttp/psr7": "^2.11", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.24" }, "provide": { "psr/http-client-implementation": "1.0" @@ -1206,7 +1207,7 @@ "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", "guzzle/client-integration-tests": "3.0.2", - "guzzlehttp/test-server": "^0.4", + "guzzlehttp/test-server": "^0.5", "php-http/message-factory": "^1.1", "phpunit/phpunit": "^8.5.52 || ^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" @@ -1286,7 +1287,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.5" + "source": "https://github.com/guzzle/guzzle/tree/7.11.1" }, "funding": [ { @@ -1302,24 +1303,25 @@ "type": "tidelift" } ], - "time": "2026-05-27T11:53:46+00:00" + "time": "2026-06-07T22:54:06+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.4.1", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2" + "reference": "4360e982f87f5f258bf872d094647791db2f4c8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/09e8a212562fb1fb6a512c4156ed71525969d6c2", - "reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2", + "url": "https://api.github.com/repos/guzzle/promises/zipball/4360e982f87f5f258bf872d094647791db2f4c8e", + "reference": "4360e982f87f5f258bf872d094647791db2f4c8e", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "php": "^7.2.5 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", @@ -1369,7 +1371,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.4.1" + "source": "https://github.com/guzzle/promises/tree/2.5.0" }, "funding": [ { @@ -1385,27 +1387,29 @@ "type": "tidelift" } ], - "time": "2026-05-20T22:57:30+00:00" + "time": "2026-06-02T12:23:43+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.10.3", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "7c1472269227dc6f18930bd903d7a88fe6c52130" + "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/7c1472269227dc6f18930bd903d7a88fe6c52130", - "reference": "7c1472269227dc6f18930bd903d7a88fe6c52130", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/bbb5e61349fa5cb822b3e87842b951088b76b81f", + "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0" + "ralouphie/getallheaders": "^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.24" }, "provide": { "psr/http-factory-implementation": "1.0", @@ -1486,7 +1490,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.10.3" + "source": "https://github.com/guzzle/psr7/tree/2.11.0" }, "funding": [ { @@ -1502,7 +1506,7 @@ "type": "tidelift" } ], - "time": "2026-05-27T11:48:20+00:00" + "time": "2026-06-02T12:30:48+00:00" }, { "name": "guzzlehttp/uri-template", @@ -1803,16 +1807,16 @@ }, { "name": "laravel/framework", - "version": "v12.61.0", + "version": "v12.61.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "1124062a1ca92d290c8bcb9b7f649920fa6816bf" + "reference": "e8472ca9774452fe50841d9bdced060679f4d58d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/1124062a1ca92d290c8bcb9b7f649920fa6816bf", - "reference": "1124062a1ca92d290c8bcb9b7f649920fa6816bf", + "url": "https://api.github.com/repos/laravel/framework/zipball/e8472ca9774452fe50841d9bdced060679f4d58d", + "reference": "e8472ca9774452fe50841d9bdced060679f4d58d", "shasum": "" }, "require": { @@ -2021,7 +2025,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-05-26T23:41:33+00:00" + "time": "2026-06-04T14:22:52+00:00" }, { "name": "laravel/prompts", @@ -4191,16 +4195,16 @@ }, { "name": "predis/predis", - "version": "v3.4.2", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "2033429520d8997a7815a2485f56abe6d2d0e075" + "reference": "8cc4319c06924c8ff0c5c7eec4243a19e3be32f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/2033429520d8997a7815a2485f56abe6d2d0e075", - "reference": "2033429520d8997a7815a2485f56abe6d2d0e075", + "url": "https://api.github.com/repos/predis/predis/zipball/8cc4319c06924c8ff0c5c7eec4243a19e3be32f1", + "reference": "8cc4319c06924c8ff0c5c7eec4243a19e3be32f1", "shasum": "" }, "require": { @@ -4242,7 +4246,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v3.4.2" + "source": "https://github.com/predis/predis/tree/v3.5.0" }, "funding": [ { @@ -4250,7 +4254,7 @@ "type": "github" } ], - "time": "2026-03-09T20:33:04+00:00" + "time": "2026-06-02T19:25:56+00:00" }, { "name": "psr/clock", @@ -6845,16 +6849,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.38.1", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92" + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92", - "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { @@ -6906,7 +6910,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -6926,7 +6930,7 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:51:13+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { "name": "symfony/polyfill-php80", @@ -7014,16 +7018,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.38.1", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "8339098cae28673c15cce00d80734af0453054e2" + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/8339098cae28673c15cce00d80734af0453054e2", - "reference": "8339098cae28673c15cce00d80734af0453054e2", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8", "shasum": "" }, "require": { @@ -7070,7 +7074,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2" }, "funding": [ { @@ -7090,7 +7094,7 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:51:13+00:00" + "time": "2026-05-27T06:51:48+00:00" }, { "name": "symfony/polyfill-php84", @@ -9179,11 +9183,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.2.0", + "version": "2.2.2", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b4cd98348c809924f62bb9cc8c047f5e73bc9a58", - "reference": "b4cd98348c809924f62bb9cc8c047f5e73bc9a58", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e5cc34d491a90e79c216d824f60fe21fd4d93bd6", + "reference": "e5cc34d491a90e79c216d824f60fe21fd4d93bd6", "shasum": "" }, "require": { @@ -9239,7 +9243,7 @@ "type": "github" } ], - "time": "2026-05-28T08:22:43+00:00" + "time": "2026-06-05T09:00:01+00:00" }, { "name": "phpunit/php-code-coverage", From c74df7e06bb5dcc8c89952b40d277a87ea85807d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 9 Jun 2026 12:50:16 +0100 Subject: [PATCH 182/204] Updated translator & dependency attribution before release v26.05.1 --- .github/translators.txt | 2 ++ dev/licensing/js-library-licenses.txt | 29 +++++---------------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/.github/translators.txt b/.github/translators.txt index cae26547bd3..6aa2be470f3 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -544,3 +544,5 @@ FelixFrizzy :: German Pedro de Mattia (pdmtt) :: Portuguese, Brazilian lonestan :: Russian Paul Kernstock (kernstock) :: German +brtbr :: German; German Informal +Ricardo Covelo (covelo12) :: Portuguese diff --git a/dev/licensing/js-library-licenses.txt b/dev/licensing/js-library-licenses.txt index 45d1816dd17..bd3e09f9f6e 100644 --- a/dev/licensing/js-library-licenses.txt +++ b/dev/licensing/js-library-licenses.txt @@ -482,8 +482,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: git+https://github.com/Kilian/electron-to-chromium.git +Link: git+https://github.com/Kilian/electron-to-chromium.git ----------- emittery License: MIT @@ -860,7 +860,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://www.handlebarsjs.com/ +Link: https://handlebarsjs.com/ ----------- has-bigints License: MIT @@ -1663,13 +1663,6 @@ 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 @@ -3574,13 +3567,6 @@ 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 @@ -3603,8 +3589,8 @@ Link: https://github.com/un-ts/pkgr/blob/master/packages/core License: MIT License File: node_modules/@sinclair/typebox/license Copyright: Copyright (c) 2017-2026 Haydn Paterson -Source: https://github.com/sinclairzx81/typebox-legacy -Link: https://github.com/sinclairzx81/typebox-legacy +Source: https://github.com/sinclairzx81/sinclair-typebox +Link: https://github.com/sinclairzx81/sinclair-typebox ----------- @sinonjs/commons License: BSD-3-Clause @@ -3817,8 +3803,3 @@ 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 From dad83d473d397efa9f437011c4cffb43cbf8c30d Mon Sep 17 00:00:00 2001 From: PolarniMeda Date: Thu, 11 Jun 2026 10:32:03 +0200 Subject: [PATCH 183/204] Added Serbian language to language_select array --- lang/en/settings.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lang/en/settings.php b/lang/en/settings.php index d03024a89d6..0e5ce84cf21 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', From 79a2e017bbf5d4d634e890199e3267d9280f1db1 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 11 Jun 2026 14:24:09 +0100 Subject: [PATCH 184/204] Maintenance: Fixed type and CI issues - Fixed issues picked up by PHPStan updates. - Not sure why it was flagging the BookSorter issue, but swapping if statements made it go away. - Updated BookSortMapItem with modern syntax. - Attempted to fix CI issues by adding DOM extension. - Attempted to make migration CI more efficient via tmpfs --- .forgejo/workflows/analyse-php.yml | 2 +- .forgejo/workflows/test-migrations.yml | 10 ++++- .forgejo/workflows/test-php.yml | 2 +- app/Sorting/BookSortMapItem.php | 39 ++++--------------- app/Sorting/BookSorter.php | 2 +- .../HtmlPurifier/ConfiguredHtmlPurifier.php | 1 - 6 files changed, 19 insertions(+), 37 deletions(-) diff --git a/.forgejo/workflows/analyse-php.yml b/.forgejo/workflows/analyse-php.yml index 8975d6e5296..3a07d9bd5c0 100644 --- a/.forgejo/workflows/analyse-php.yml +++ b/.forgejo/workflows/analyse-php.yml @@ -22,7 +22,7 @@ jobs: uses: https://github.com/shivammathur/setup-php@v2 with: php-version: 8.5 - extensions: gd, mbstring, json, curl, xml, mysql, ldap + extensions: gd, mbstring, json, curl, xml, dom, mysql, ldap - name: Get Composer Cache Directory id: composer-cache diff --git a/.forgejo/workflows/test-migrations.yml b/.forgejo/workflows/test-migrations.yml index e969d3e4721..5848f54d901 100644 --- a/.forgejo/workflows/test-migrations.yml +++ b/.forgejo/workflows/test-migrations.yml @@ -23,6 +23,14 @@ jobs: 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 @@ -35,7 +43,7 @@ jobs: uses: https://github.com/shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: gd, mbstring, json, curl, xml, mysql, ldap + extensions: gd, mbstring, json, curl, xml, dom, mysql, ldap - name: Get Composer Cache Directory id: composer-cache diff --git a/.forgejo/workflows/test-php.yml b/.forgejo/workflows/test-php.yml index 06a6de276d2..99e1af518f7 100644 --- a/.forgejo/workflows/test-php.yml +++ b/.forgejo/workflows/test-php.yml @@ -21,7 +21,7 @@ jobs: matrix: php: ['8.2', '8.3', '8.4', '8.5'] env: - phpextensions: gd, mbstring, json, curl, xml, mysql, ldap, gmp + phpextensions: gd, mbstring, json, curl, xml, dom, mysql, ldap, gmp phpextensioncachekey: cache-v1 steps: - uses: https://code.forgejo.org/actions/checkout@v6 diff --git a/app/Sorting/BookSortMapItem.php b/app/Sorting/BookSortMapItem.php index 8f517edd6ff..40137fc49fa 100644 --- a/app/Sorting/BookSortMapItem.php +++ b/app/Sorting/BookSortMapItem.php @@ -4,37 +4,12 @@ class BookSortMapItem { - /** - * @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; + public function __construct( + public int $id, + public int $sort, + public int|null $parentChapterId, + public string $type, + public int $parentBookId, + ) { } } diff --git a/app/Sorting/BookSorter.php b/app/Sorting/BookSorter.php index 0862aaa8877..63c66b0fd7a 100644 --- a/app/Sorting/BookSorter.php +++ b/app/Sorting/BookSorter.php @@ -168,7 +168,7 @@ protected function applySortUpdates(BookSortMapItem $sortMapItem, array $modelMa $model->priority = $sortMapItem->sort; } - if ($chapterChanged || $priorityChanged) { + if ($priorityChanged || $chapterChanged) { $model::withoutTimestamps(fn () => $model->save()); } } diff --git a/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php b/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php index 307cdb74f28..87ed5add28e 100644 --- a/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php +++ b/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php @@ -38,7 +38,6 @@ public function __construct() $this->configureHtmlDefinition($htmlDef); } - /** @var \HTMLPurifier_URIDefinition $uriDef */ $uriDef = $config->getDefinition('URI', true, true); if ($uriDef instanceof HTMLPurifier_URIDefinition) { $this->configureUriDefinition($uriDef); From c511f7d9356a73cb96e6ecd605736946dcfb6cbd Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 24 Jun 2026 13:48:20 +0100 Subject: [PATCH 185/204] CI: Added workflow to sync with snyk --- .forgejo/workflows/update-snyk.yml | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .forgejo/workflows/update-snyk.yml diff --git a/.forgejo/workflows/update-snyk.yml b/.forgejo/workflows/update-snyk.yml new file mode 100644 index 00000000000..64df65b540e --- /dev/null +++ b/.forgejo/workflows/update-snyk.yml @@ -0,0 +1,35 @@ +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: + command: monitor + args: --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: + command: monitor + args: --file=package-lock.json --project-name=bookstack-${{forgejo.ref_name}}-npm \ No newline at end of file From f7df78b91b12ff1d8e248ded7747e76203904b8e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 24 Jun 2026 13:54:01 +0100 Subject: [PATCH 186/204] CI: Attempted to fix snyk workflow --- .forgejo/workflows/update-snyk.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.forgejo/workflows/update-snyk.yml b/.forgejo/workflows/update-snyk.yml index 64df65b540e..89ce68ae3a4 100644 --- a/.forgejo/workflows/update-snyk.yml +++ b/.forgejo/workflows/update-snyk.yml @@ -23,13 +23,11 @@ jobs: env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: - command: monitor - args: --file=composer.lock --project-name=bookstack-${{forgejo.ref_name}}-composer + 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: - command: monitor - args: --file=package-lock.json --project-name=bookstack-${{forgejo.ref_name}}-npm \ No newline at end of file + args: snyk monitor --file=package-lock.json --project-name=bookstack-${{forgejo.ref_name}}-npm \ No newline at end of file From 59bbf504cf1c6205c56c9c0d6c9e3c6154904ecc Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 30 Jun 2026 18:35:34 +0100 Subject: [PATCH 187/204] Content filtering: Added srcset protocol filter Upstream libraries used did not specifically treat values in srcset as URIs like other attributes, so this adds a simple filter for possible bad values. Updated tests to cover. Thanks for Gurmandeep Deol for reporting. --- .../HtmlPurifier/ConfiguredHtmlPurifier.php | 4 +++ .../Filters/UriLimitFileProtocolToAnchors.php | 2 -- app/Util/HtmlPurifier/SrcsetAttrDef.php | 26 +++++++++++++++++++ tests/Entity/PageContentFilteringTest.php | 3 +++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 app/Util/HtmlPurifier/SrcsetAttrDef.php diff --git a/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php b/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php index 87ed5add28e..1739359037f 100644 --- a/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php +++ b/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php @@ -156,6 +156,10 @@ protected function configureHtmlDefinition(HTMLPurifier_HTMLDefinition $definiti // 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 diff --git a/app/Util/HtmlPurifier/Filters/UriLimitFileProtocolToAnchors.php b/app/Util/HtmlPurifier/Filters/UriLimitFileProtocolToAnchors.php index 19ca9cc82b9..bf259e08398 100644 --- a/app/Util/HtmlPurifier/Filters/UriLimitFileProtocolToAnchors.php +++ b/app/Util/HtmlPurifier/Filters/UriLimitFileProtocolToAnchors.php @@ -51,5 +51,3 @@ public function filter(&$uri, $config, $context) return false; } } - -// vim: et sw=4 sts=4 diff --git a/app/Util/HtmlPurifier/SrcsetAttrDef.php b/app/Util/HtmlPurifier/SrcsetAttrDef.php new file mode 100644 index 00000000000..3b8417a55e2 --- /dev/null +++ b/app/Util/HtmlPurifier/SrcsetAttrDef.php @@ -0,0 +1,26 @@ +
    ' => '', '
    My local image
    ' => '
    ', '
    My local image
    ' => '
    ', + '
    My local image
    ' => '
    My local image
    ', + '
    cat
    ' => '
    cat
    ', + '' => '', ]; config()->set('app.content_filtering', 'a'); From 01dc1e71c5574ba36dabb55d9b2a3e4a234df2cc Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 30 Jun 2026 23:50:11 +0100 Subject: [PATCH 188/204] Attachments: Added more extensive URL filtering Added a central URLFilter class to check & clean URLs used for attachments, which is also used for validation, and by the purifier to standardise protocols (and to make protocol config easier in future). Thanks to mfk25 for reporting. --- .../ValidationRuleServiceProvider.php | 7 +- app/Uploads/Attachment.php | 3 +- .../Controllers/AttachmentController.php | 4 +- .../HtmlPurifier/ConfiguredHtmlPurifier.php | 18 ++- app/Util/UrlFilter.php | 103 ++++++++++++++++++ tests/Uploads/AttachmentTest.php | 28 +++++ tests/Util/UrlFilterTest.php | 71 ++++++++++++ 7 files changed, 218 insertions(+), 16 deletions(-) create mode 100644 app/Util/UrlFilter.php create mode 100644 tests/Util/UrlFilterTest.php diff --git a/app/App/Providers/ValidationRuleServiceProvider.php b/app/App/Providers/ValidationRuleServiceProvider.php index 1adc1ebd851..fc030263029 100644 --- a/app/App/Providers/ValidationRuleServiceProvider.php +++ b/app/App/Providers/ValidationRuleServiceProvider.php @@ -3,6 +3,7 @@ namespace BookStack\App\Providers; use BookStack\Uploads\ImageService; +use BookStack\Util\UrlFilter; use Illuminate\Support\Facades\Validator; use Illuminate\Support\ServiceProvider; @@ -21,10 +22,8 @@ public function boot(): void Validator::extend('safe_url', function ($attribute, $value, $parameters, $validator) { $cleanLinkName = strtolower(trim($value)); - $isJs = str_starts_with($cleanLinkName, 'javascript:'); - $isData = str_starts_with($cleanLinkName, 'data:'); - - return !$isJs && !$isData; + $filter = new UrlFilter($cleanLinkName); + return $filter->isAllowed(); }); } } diff --git a/app/Uploads/Attachment.php b/app/Uploads/Attachment.php index 05227243a76..1619bebbcdd 100644 --- a/app/Uploads/Attachment.php +++ b/app/Uploads/Attachment.php @@ -10,6 +10,7 @@ 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; @@ -71,7 +72,7 @@ public function jointPermissions(): HasMany public function getUrl($openInline = false): string { if ($this->external && !str_starts_with($this->path, 'http')) { - return $this->path; + return (new UrlFilter($this->path))->clean(); } return url('/attachments/' . $this->id . ($openInline ? '?open=true' : '')); diff --git a/app/Uploads/Controllers/AttachmentController.php b/app/Uploads/Controllers/AttachmentController.php index bdd96d6ad7b..aa9e0e29195 100644 --- a/app/Uploads/Controllers/AttachmentController.php +++ b/app/Uploads/Controllers/AttachmentController.php @@ -11,6 +11,7 @@ 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; @@ -221,7 +222,8 @@ public function get(Request $request, string $attachmentId) } if ($attachment->external) { - return redirect($attachment->path); + $url = (new UrlFilter($attachment->path))->clean(); + return redirect($url); } $fileName = $attachment->getFileName(); diff --git a/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php b/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php index 1739359037f..221db6ffc39 100644 --- a/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php +++ b/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php @@ -4,6 +4,7 @@ use BookStack\App\AppVersion; use BookStack\Util\HtmlPurifier\Filters\UriLimitFileProtocolToAnchors; +use BookStack\Util\UrlFilter; use HTMLPurifier; use HTMLPurifier_Config; use HTMLPurifier_DefinitionCache_Serializer; @@ -84,16 +85,13 @@ protected function setConfig(HTMLPurifier_Config $config, string $cachePath): vo $config->set('Attr.ID.HTML5', true); $config->set('Output.FixInnerHTML', false); $config->set('URI.SafeIframeRegexp', '%^(http://|https://|//)%'); - $config->set('URI.AllowedSchemes', [ - 'http' => true, - 'https' => true, - 'mailto' => true, - 'ftp' => true, - 'nntp' => true, - 'news' => true, - 'tel' => true, - 'file' => true, - ]); + + $allowedSchemes = UrlFilter::getAllowedSchemes(); + $allowedSchemesSetting = []; + foreach ($allowedSchemes as $scheme) { + $allowedSchemesSetting[$scheme] = true; + } + $config->set('URI.AllowedSchemes', $allowedSchemesSetting); // $config->set('Cache.DefinitionImpl', null); // Disable cache during testing } diff --git a/app/Util/UrlFilter.php b/app/Util/UrlFilter.php new file mode 100644 index 00000000000..a3c65d03ec3 --- /dev/null +++ b/app/Util/UrlFilter.php @@ -0,0 +1,103 @@ +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; + } +} diff --git a/tests/Uploads/AttachmentTest.php b/tests/Uploads/AttachmentTest.php index 22e4f4610a0..3f8a2aaf14f 100644 --- a/tests/Uploads/AttachmentTest.php +++ b/tests/Uploads/AttachmentTest.php @@ -315,6 +315,34 @@ public function test_data_and_js_links_cannot_be_attached_to_a_page() } } + public function test_existing_data_and_js_links_do_not_render_link() + { + $this->asAdmin(); + $page = $this->entities->page(); + $attachment = Attachment::factory()->create(['uploaded_to' => $page->id]); + + $links = [ + 'javascript:alert("bunny")', + ' javascript:alert("bunny")', + 'JavaScript:alert("bunny")', + "\t\n\t\nJavaScript:alert(\"bunny\")", + 'data:text/html;bunny', + 'Data:text/html;bunny', + 'Data:text/html;bunny', + 'donk\tscript:alert("bunny")', + "donk\tscript:alert('bunny')", + ]; + + foreach ($links as $link) { + $attachment->path = $link; + $attachment->save(); + + $resp = $this->get($page->getUrl()); + $resp->assertDontSee('bunny', false); + $resp->assertSee('#badlink', false); + } + } + public function test_attachment_delete_only_shows_with_permission() { $this->asAdmin(); diff --git a/tests/Util/UrlFilterTest.php b/tests/Util/UrlFilterTest.php new file mode 100644 index 00000000000..8811b46d9b6 --- /dev/null +++ b/tests/Util/UrlFilterTest.php @@ -0,0 +1,71 @@ +', + 'Data:text/html;bunny', + 'Data:text/html;bunny', + "http://example.com\0javascript:alert(1)", + ]; + + foreach ($urls as $url) { + $filter = new UrlFilter($url); + $this->assertFalse($filter->isAllowed(), "Failed to detect invalid url: {$url}"); + } + } + + public function test_clean() + { + $expectedOutputByInput = [ + 'javascript:alert("bunny")' => '#badlink', + ' javascript:alert("bunny")' => '#badlink', + 'JavaScript:alert("bunny")' => '#badlink', + "\t\n\t\nJavaScript:alert(\"bunny\")" => '#badlink', + 'data:text/html;bunny' => '#badlink', + 'Data:text/html;bunny' => '#badlink', + 'Data:text/html;bunny' => '#badlink', + "http://example.com\0javascript:alert(1)" => '#badlink', + "Java\tScript:alert(\"bunny\")" => '#badlink', + + 'https://example.com' => 'https://example.com', + 'https://example.com/a/b' => 'https://example.com/a/b', + 'https://example.com/a/b?a=b#ab' => 'https://example.com/a/b?a=b#ab', + 'https://example.com/a/b?a=b#ab&c=d' => 'https://example.com/a/b?a=b#ab&c=d', + 'https://example.com:5050' => 'https://example.com:5050', + 'https://example.com:5050/a/b' => 'https://example.com:5050/a/b', + 'https://example.com:5050/a/b?a=b#ab' => 'https://example.com:5050/a/b?a=b#ab', + 'https://user@example.com:5011/a/b?a=b' => 'https://user@example.com:5011/a/b?a=b', + 'https://user:pass@example.com:5011/a/b?a=b' => 'https://user:pass@example.com:5011/a/b?a=b', + + '//example.com' => 'https://example.com', + 'a/b/c' => 'a/b/c', + '/a/b/c' => '/a/b/c', + + 'tel:123456789' => 'tel:123456789', + 'TEL:123456789' => 'tel:123456789', + 'maiLto:a@b.c' => 'mailto:a@b.c', + 'file://a/b/c' => 'file://a/b/c', + 'ftp://a/b/c' => 'ftp://a/b/c', + 'nntp://a/b/c' => 'nntp://a/b/c', + 'news:a/b/c' => 'news:a/b/c', + ]; + + foreach ($expectedOutputByInput as $input => $expected) { + $filter = new UrlFilter($input); + $output = $filter->clean(); + $this->assertEquals($expected, $output, "Failed to clean url: {$input}"); + } + } +} From fe39b69c1f626e09bc5066b9e76a9f29aab14a8d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 1 Jul 2026 10:20:32 +0100 Subject: [PATCH 189/204] Comments: Added visibility check to comment delete Aligns it with other actions/endpoints, and ensures an extra layer of control against malicious use. Thanks to mfk25 for reporting. --- app/Activity/Controllers/CommentController.php | 11 ++++------- tests/Activity/CommentStoreTest.php | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/Activity/Controllers/CommentController.php b/app/Activity/Controllers/CommentController.php index f61a2c8df6e..8474d9eb1c7 100644 --- a/app/Activity/Controllers/CommentController.php +++ b/app/Activity/Controllers/CommentController.php @@ -59,8 +59,7 @@ public function update(Request $request, int $commentId) 'html' => ['required', 'string'], ]); - $comment = $this->commentRepo->getById($commentId); - $this->checkOwnablePermission(Permission::PageView, $comment->entity); + $comment = $this->commentRepo->getVisibleById($commentId); $this->checkOwnablePermission(Permission::CommentUpdate, $comment); $comment = $this->commentRepo->update($comment, $input['html']); @@ -76,8 +75,7 @@ public function update(Request $request, int $commentId) */ public function archive(int $id) { - $comment = $this->commentRepo->getById($id); - $this->checkOwnablePermission(Permission::PageView, $comment->entity); + $comment = $this->commentRepo->getVisibleById($id); if (!userCan(Permission::CommentUpdate, $comment) && !userCan(Permission::CommentDelete, $comment)) { $this->showPermissionError(); } @@ -96,8 +94,7 @@ public function archive(int $id) */ public function unarchive(int $id) { - $comment = $this->commentRepo->getById($id); - $this->checkOwnablePermission(Permission::PageView, $comment->entity); + $comment = $this->commentRepo->getVisibleById($id); if (!userCan(Permission::CommentUpdate, $comment) && !userCan(Permission::CommentDelete, $comment)) { $this->showPermissionError(); } @@ -116,7 +113,7 @@ public function unarchive(int $id) */ public function destroy(int $id) { - $comment = $this->commentRepo->getById($id); + $comment = $this->commentRepo->getVisibleById($id); $this->checkOwnablePermission(Permission::CommentDelete, $comment); $this->commentRepo->delete($comment); diff --git a/tests/Activity/CommentStoreTest.php b/tests/Activity/CommentStoreTest.php index 2296f91a912..04624e4d5f9 100644 --- a/tests/Activity/CommentStoreTest.php +++ b/tests/Activity/CommentStoreTest.php @@ -105,6 +105,23 @@ public function test_comment_delete() $this->assertActivityExists(ActivityType::COMMENT_DELETE); } + public function test_comment_delete_requires_view_permission_to_page() + { + $editor = $this->users->editor(); + $this->permissions->grantUserRolePermissions($editor, ['comment-delete-all']); + $page = $this->entities->page(); + $this->actingAs($editor); + + $commentData = Comment::factory()->make(); + $this->postJson("/comment/$page->id", $commentData->getAttributes()); + $comment = $page->comments()->first(); + $this->permissions->disableEntityInheritedPermissions($page); + + $resp = $this->deleteJson("/comment/$comment->id"); + $resp->assertStatus(404); + $this->assertDatabaseHas('comments', ['id' => $comment->id]); + } + public function test_comment_archive_and_unarchive() { $this->asAdmin(); From caeea658d117580f801ef55aa450aec2f16d785b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 1 Jul 2026 10:45:59 +0100 Subject: [PATCH 190/204] Access: Hardened usage of referring URLs via login Adds a more substantial URL check, via a new class which is shared and used in other parts of the app for consistency. Thanks to mfk25 for reporting. --- app/Access/Controllers/LoginController.php | 4 +- app/Console/Commands/InstallModuleCommand.php | 9 ++- app/Util/UrlComparison.php | 36 +++++++++++ tests/Auth/AuthTest.php | 16 +++-- tests/Util/UrlComparisonTest.php | 60 +++++++++++++++++++ 5 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 app/Util/UrlComparison.php create mode 100644 tests/Util/UrlComparisonTest.php diff --git a/app/Access/Controllers/LoginController.php b/app/Access/Controllers/LoginController.php index 4694f22e4d3..fece3d88098 100644 --- a/app/Access/Controllers/LoginController.php +++ b/app/Access/Controllers/LoginController.php @@ -8,6 +8,7 @@ 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; @@ -186,7 +187,8 @@ protected function updateIntendedFromPrevious(): void { // Store the previous location for redirect after login $previous = url()->previous(''); - $isPreviousFromInstance = str_starts_with($previous, url('/')); + $comparison = new UrlComparison($previous, url('/')); + $isPreviousFromInstance = $comparison->originsMatch() && $comparison->pathsOverlap(); if (!$previous || !setting('app-public') || !$isPreviousFromInstance) { return; } diff --git a/app/Console/Commands/InstallModuleCommand.php b/app/Console/Commands/InstallModuleCommand.php index 114bfb105d8..8e77474554e 100644 --- a/app/Console/Commands/InstallModuleCommand.php +++ b/app/Console/Commands/InstallModuleCommand.php @@ -7,6 +7,7 @@ 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; @@ -199,7 +200,6 @@ protected function downloadModuleFile(string $location): string|null { $httpRequests = app()->make(HttpRequestService::class); $client = $httpRequests->buildClient(30, ['stream' => true]); - $originalUrl = parse_url($location); $currentLocation = $location; $maxRedirects = 3; $redirectCount = 0; @@ -212,12 +212,11 @@ protected function downloadModuleFile(string $location): string|null if ($statusCode >= 300 && $statusCode < 400 && $redirectCount < $maxRedirects) { $redirectLocation = $resp->getHeaderLine('Location'); if ($redirectLocation) { - $redirectUrl = parse_url($redirectLocation); - $redirectOriginMatches = ($originalUrl['host'] ?? '') === ($redirectUrl['host'] ?? '') - && ($originalUrl['scheme'] ?? '') === ($redirectUrl['scheme'] ?? '') - && ($originalUrl['port'] ?? '') === ($redirectUrl['port'] ?? ''); + $comparison = new UrlComparison($location, $redirectLocation); + $redirectOriginMatches = $comparison->originsMatch(); 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?"); diff --git a/app/Util/UrlComparison.php b/app/Util/UrlComparison.php new file mode 100644 index 00000000000..66984918d29 --- /dev/null +++ b/app/Util/UrlComparison.php @@ -0,0 +1,36 @@ +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); + } +} diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index bffd8bbdbcb..b42f7cb40d6 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -70,10 +70,18 @@ public function test_login_intended_redirect_does_not_redirect_to_external_pages config()->set('app.url', 'http://localhost'); $this->setSettings(['app-public' => true]); - $this->get('/login', ['referer' => 'https://example.com']); - $login = $this->post('/login', ['email' => 'admin@admin.com', 'password' => 'password']); - - $login->assertRedirect('http://localhost'); + $testCases = [ + 'https://example.com', + 'http://localhost.example.com', + 'http://localhost:ab@example.com', + ]; + + foreach ($testCases as $testCase) { + $this->get('/login', ['Referer' => $testCase]); + $login = $this->post('/login', ['email' => 'admin@admin.com', 'password' => 'password']); + $login->assertRedirect('http://localhost'); + auth()->logout(); + } } public function test_login_intended_redirect_does_not_factor_mfa_routes() diff --git a/tests/Util/UrlComparisonTest.php b/tests/Util/UrlComparisonTest.php new file mode 100644 index 00000000000..af20011fa60 --- /dev/null +++ b/tests/Util/UrlComparisonTest.php @@ -0,0 +1,60 @@ +assertTrue($comparison->originsMatch()); + } + + foreach ($bad as [$a, $b]) { + $comparison = new UrlComparison($a, $b); + $this->assertFalse($comparison->originsMatch()); + } + } + + public function test_paths_overlap() + { + $good = [ + ['https://example.com', 'https://example.com/a/b/c'], + ['https://example.com/', 'https://example.com/a/b/c'], + ['https://example.com/a/b', 'https://example.com/a/b/c'], + ['https://example.com/a/b/c', 'https://example.com/a'], + ['http://donk.com/a/b/c?a=b#cat', 'https://example.com:5005/a/b#hello'], + ]; + + $bad = [ + ['https://example.com/a/c', 'https://example.com/a/b/c/d'], + ['https://example.com/a/c', 'https://example.com/d/a/c'], + ]; + + foreach ($good as [$a, $b]) { + $comparison = new UrlComparison($a, $b); + $this->assertTrue($comparison->pathsOverlap()); + } + + foreach ($bad as [$a, $b]) { + $comparison = new UrlComparison($a, $b); + $this->assertFalse($comparison->pathsOverlap()); + } + } +} From b6e3d304feb199f68b2d04c41956b1b4e5371a72 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Thu, 2 Jul 2026 08:34:05 +0000 Subject: [PATCH 191/204] New Crowdin translations by GitHub Action --- lang/ar/settings.php | 1 + lang/bg/settings.php | 1 + lang/bn/settings.php | 1 + lang/bs/settings.php | 1 + lang/ca/settings.php | 1 + lang/cs/settings.php | 1 + lang/cy/settings.php | 1 + lang/da/settings.php | 1 + lang/de/settings.php | 1 + lang/de_informal/settings.php | 1 + lang/el/settings.php | 1 + lang/es/settings.php | 1 + lang/es_AR/settings.php | 1 + lang/et/settings.php | 1 + lang/eu/settings.php | 1 + lang/fa/settings.php | 1 + lang/fi/settings.php | 1 + lang/fr/settings.php | 1 + lang/he/settings.php | 1 + lang/hr/settings.php | 1 + lang/hu/settings.php | 1 + lang/id/activities.php | 2 +- lang/id/editor.php | 4 +- lang/id/settings.php | 1 + lang/is/settings.php | 1 + lang/it/settings.php | 3 +- lang/ja/settings.php | 1 + lang/ka/settings.php | 1 + lang/ko/settings.php | 1 + lang/ku/settings.php | 1 + lang/lt/settings.php | 1 + lang/lv/settings.php | 1 + lang/nb/settings.php | 1 + lang/ne/settings.php | 1 + lang/nl/settings.php | 1 + lang/nn/settings.php | 1 + lang/pl/activities.php | 4 +- lang/pl/auth.php | 2 +- lang/pl/entities.php | 10 +- lang/pl/errors.php | 2 +- lang/pl/settings.php | 11 +- lang/pt/activities.php | 4 +- lang/pt/auth.php | 2 +- lang/pt/editor.php | 12 +- lang/pt/entities.php | 90 ++-- lang/pt/errors.php | 34 +- lang/pt/notifications.php | 6 +- lang/pt/preferences.php | 4 +- lang/pt/settings.php | 97 ++--- lang/pt/validation.php | 10 +- lang/pt_BR/activities.php | 4 +- lang/pt_BR/auth.php | 2 +- lang/pt_BR/entities.php | 8 +- lang/pt_BR/settings.php | 13 +- lang/ro/settings.php | 1 + lang/ru/settings.php | 1 + lang/sk/settings.php | 1 + lang/sl/settings.php | 1 + lang/sq/settings.php | 1 + lang/sr/activities.php | 36 +- lang/sr/auth.php | 6 +- lang/sr/common.php | 18 +- lang/sr/editor.php | 14 +- lang/sr/entities.php | 784 +++++++++++++++++----------------- lang/sr/errors.php | 174 ++++---- lang/sr/notifications.php | 12 +- lang/sr/pagination.php | 4 +- lang/sr/passwords.php | 10 +- lang/sr/preferences.php | 78 ++-- lang/sr/settings.php | 439 +++++++++---------- lang/sr/validation.php | 170 ++++---- lang/sv/settings.php | 1 + lang/th/settings.php | 1 + lang/tk/settings.php | 1 + lang/tr/settings.php | 1 + lang/uk/settings.php | 3 +- lang/uz/settings.php | 1 + lang/vi/settings.php | 1 + lang/zh_CN/auth.php | 2 +- lang/zh_CN/entities.php | 8 +- lang/zh_CN/settings.php | 7 +- lang/zh_TW/activities.php | 4 +- lang/zh_TW/auth.php | 2 +- lang/zh_TW/entities.php | 8 +- lang/zh_TW/errors.php | 2 +- lang/zh_TW/settings.php | 13 +- 86 files changed, 1107 insertions(+), 1055 deletions(-) diff --git a/lang/ar/settings.php b/lang/ar/settings.php index aa361e04b9a..af02a556411 100644 --- a/lang/ar/settings.php +++ b/lang/ar/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/bg/settings.php b/lang/bg/settings.php index 0af97414045..d347510ff0d 100644 --- a/lang/bg/settings.php +++ b/lang/bg/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/bn/settings.php b/lang/bn/settings.php index ab7fe951271..5bceb85f8a4 100644 --- a/lang/bn/settings.php +++ b/lang/bn/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/bs/settings.php b/lang/bs/settings.php index d03024a89d6..0e5ce84cf21 100644 --- a/lang/bs/settings.php +++ b/lang/bs/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/ca/settings.php b/lang/ca/settings.php index ac60ce8e2cb..d5a70545977 100644 --- a/lang/ca/settings.php +++ b/lang/ca/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/cs/settings.php b/lang/cs/settings.php index 85c479dbddb..92425b5e4e0 100644 --- a/lang/cs/settings.php +++ b/lang/cs/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/cy/settings.php b/lang/cy/settings.php index 816a4b89ffb..8ac7bbc50b9 100644 --- a/lang/cy/settings.php +++ b/lang/cy/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/da/settings.php b/lang/da/settings.php index 9d83f5004db..c28c9fef0b8 100644 --- a/lang/da/settings.php +++ b/lang/da/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'Thailandsk', 'tr' => 'Türkçe', diff --git a/lang/de/settings.php b/lang/de/settings.php index 29e857098d6..a202898cf4e 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Russisch', 'sk' => 'Slowenisch', 'sl' => 'Slowenisch', + 'sr' => 'Српски', 'sv' => 'Schwedisch', 'th' => 'ภาษาไทย', 'tr' => 'Türkisch', diff --git a/lang/de_informal/settings.php b/lang/de_informal/settings.php index 7c1796afcc6..f02f6e02559 100644 --- a/lang/de_informal/settings.php +++ b/lang/de_informal/settings.php @@ -367,6 +367,7 @@ 'ru' => 'Russisch', 'sk' => 'Slowenisch', 'sl' => 'Slowenisch', + 'sr' => 'Српски', 'sv' => 'Schwedisch', 'th' => 'ภาษาไทย', 'tr' => 'Türkisch', diff --git a/lang/el/settings.php b/lang/el/settings.php index 42422a8e7da..01379e71bba 100644 --- a/lang/el/settings.php +++ b/lang/el/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/es/settings.php b/lang/es/settings.php index 2fe672f83db..1f1b84a310e 100644 --- a/lang/es/settings.php +++ b/lang/es/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Ruso', 'sk' => 'Eslovaco', 'sl' => 'Esloveno', + 'sr' => 'Српски', 'sv' => 'Sueco', 'th' => 'ภาษาไทย', 'tr' => 'Turco', diff --git a/lang/es_AR/settings.php b/lang/es_AR/settings.php index 5545b91f3bc..0a97aca679c 100644 --- a/lang/es_AR/settings.php +++ b/lang/es_AR/settings.php @@ -367,6 +367,7 @@ 'ru' => 'Ruso', 'sk' => 'Eslovaco', 'sl' => 'Esloveno', + 'sr' => 'Српски', 'sv' => 'Sueco', 'th' => 'ภาษาไทย', 'tr' => 'Turco', diff --git a/lang/et/settings.php b/lang/et/settings.php index 3e28eae95f5..03b602372e0 100644 --- a/lang/et/settings.php +++ b/lang/et/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский (vene keel)', 'sk' => 'Slovensky', 'sl' => 'Sloveenia', + 'sr' => 'Српски', 'sv' => 'Rootsi', 'th' => 'ภาษาไทย', 'tr' => 'Türgi', diff --git a/lang/eu/settings.php b/lang/eu/settings.php index ffa2c182e81..563df987108 100644 --- a/lang/eu/settings.php +++ b/lang/eu/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/fa/settings.php b/lang/fa/settings.php index bb1b1ca7e2d..cf1413b965c 100644 --- a/lang/fa/settings.php +++ b/lang/fa/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/fi/settings.php b/lang/fi/settings.php index aa8ac3e592b..3f77d1f154f 100644 --- a/lang/fi/settings.php +++ b/lang/fi/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/fr/settings.php b/lang/fr/settings.php index 8ff81ba6bb3..697e5ffa06d 100644 --- a/lang/fr/settings.php +++ b/lang/fr/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Russe', 'sk' => 'Slovaque', 'sl' => 'Slovène', + 'sr' => 'Српски', 'sv' => 'Suédois', 'th' => 'ภาษาไทย', 'tr' => 'Turc', diff --git a/lang/he/settings.php b/lang/he/settings.php index e816766813b..4c833fae659 100644 --- a/lang/he/settings.php +++ b/lang/he/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/hr/settings.php b/lang/hr/settings.php index a595b5b1605..7d2315c85fc 100644 --- a/lang/hr/settings.php +++ b/lang/hr/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/hu/settings.php b/lang/hu/settings.php index 3aaa91c35b7..0587574a8e6 100644 --- a/lang/hu/settings.php +++ b/lang/hu/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'Thai', 'tr' => 'Türkçe', diff --git a/lang/id/activities.php b/lang/id/activities.php index db32fcf644a..edb23657ec0 100644 --- a/lang/id/activities.php +++ b/lang/id/activities.php @@ -99,7 +99,7 @@ 'user_update_notification' => 'Pengguna berhasil diperbarui', 'user_delete' => 'pengguna yang dihapus', 'user_delete_notification' => 'Pengguna berhasil dihapus', - 'user_mfa_reset' => 'reset MFA for user', + 'user_mfa_reset' => 'atur ulang MFA untuk pengguna', 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', // API Tokens diff --git a/lang/id/editor.php b/lang/id/editor.php index 04999368930..5eadf80666b 100644 --- a/lang/id/editor.php +++ b/lang/id/editor.php @@ -8,8 +8,8 @@ return [ // General editor terms 'general' => 'Umum', - 'advanced' => 'Lanjutan', - 'none' => 'Tidak Ada', + 'advanced' => 'Tingkat lanjut', + 'none' => 'Tidak Satupun', 'cancel' => 'Batal', 'save' => 'Simpan', 'close' => 'Tutup', diff --git a/lang/id/settings.php b/lang/id/settings.php index 5d785314701..73fa631ee09 100644 --- a/lang/id/settings.php +++ b/lang/id/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/is/settings.php b/lang/is/settings.php index cabe31917ef..27400e812c3 100644 --- a/lang/is/settings.php +++ b/lang/is/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/it/settings.php b/lang/it/settings.php index 3b8681840f2..2686440b8bf 100644 --- a/lang/it/settings.php +++ b/lang/it/settings.php @@ -366,8 +366,9 @@ 'ru' => 'Russo', 'sk' => 'Sloveno', 'sl' => 'Sloveno', + 'sr' => 'Српски', 'sv' => 'Svedese', - 'th' => 'ภาษาไทย', + 'th' => 'Thailandese', 'tr' => 'Turco', 'uk' => 'Ucraino', 'uz' => 'O‘zbekcha', diff --git a/lang/ja/settings.php b/lang/ja/settings.php index bca5ed0049f..5dbb35c0303 100644 --- a/lang/ja/settings.php +++ b/lang/ja/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/ka/settings.php b/lang/ka/settings.php index d03024a89d6..0e5ce84cf21 100644 --- a/lang/ka/settings.php +++ b/lang/ka/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/ko/settings.php b/lang/ko/settings.php index 90d501a7cb0..4e6ec78b406 100644 --- a/lang/ko/settings.php +++ b/lang/ko/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/ku/settings.php b/lang/ku/settings.php index d03024a89d6..0e5ce84cf21 100644 --- a/lang/ku/settings.php +++ b/lang/ku/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/lt/settings.php b/lang/lt/settings.php index 96ee2bebada..ef8053a55f3 100644 --- a/lang/lt/settings.php +++ b/lang/lt/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/lv/settings.php b/lang/lv/settings.php index 886e0ef0a0c..16c0f30ff1e 100644 --- a/lang/lv/settings.php +++ b/lang/lv/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/nb/settings.php b/lang/nb/settings.php index 1cc5d8e02ca..d859f98f4a6 100644 --- a/lang/nb/settings.php +++ b/lang/nb/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/ne/settings.php b/lang/ne/settings.php index 549a4dc8b73..3a1b5b98d53 100644 --- a/lang/ne/settings.php +++ b/lang/ne/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/nl/settings.php b/lang/nl/settings.php index 9b0fe5ad154..977aa55fcaa 100644 --- a/lang/nl/settings.php +++ b/lang/nl/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский (Russisch)', 'sk' => 'Slovensky (Slowaaks)', 'sl' => 'Slovenščina (Sloveens)', + 'sr' => 'Српски', 'sv' => 'Svenska (Zweeds)', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe (Turks)', diff --git a/lang/nn/settings.php b/lang/nn/settings.php index 08709833273..74f4c5ef330 100644 --- a/lang/nn/settings.php +++ b/lang/nn/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/pl/activities.php b/lang/pl/activities.php index 00687187d43..45920dcde34 100644 --- a/lang/pl/activities.php +++ b/lang/pl/activities.php @@ -99,8 +99,8 @@ 'user_update_notification' => 'Użytkownik zaktualizowany pomyślnie', 'user_delete' => 'usunięto użytkownika', 'user_delete_notification' => 'Użytkownik pomyślnie usunięty', - 'user_mfa_reset' => 'reset MFA for user', - 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', + 'user_mfa_reset' => 'zresetuj MFA dla użytkownika', + 'user_mfa_reset_notification' => 'Przywracanie metod uwierzytelniania wieloskładnikowego', // API Tokens 'api_token_create' => 'utworzono token API', diff --git a/lang/pl/auth.php b/lang/pl/auth.php index 7b4997a6cb0..62b684bd56c 100644 --- a/lang/pl/auth.php +++ b/lang/pl/auth.php @@ -8,7 +8,7 @@ 'failed' => 'Wprowadzone poświadczenia są nieprawidłowe.', 'throttle' => 'Zbyt wiele prób logowania. Spróbuj ponownie za :seconds s.', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Zbyt wiele prób weryfikacji wieloskładnikowej. Spróbuj ponownie za :seconds sekund.', // Login & Register 'sign_up' => 'Zarejestruj się', diff --git a/lang/pl/entities.php b/lang/pl/entities.php index ea0ac73670e..6cc7966e28f 100644 --- a/lang/pl/entities.php +++ b/lang/pl/entities.php @@ -170,10 +170,10 @@ 'books_search_this' => 'Wyszukaj w tej książce', 'books_navigation' => 'Nawigacja po książce', 'books_sort' => 'Sortuj zawartość książki', - '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_desc' => 'Przenieś rozdziały i strony w książce w celu reorganizacji jej treści. Można dodać inne książki, które umożliwiają łatwe przenoszenie rozdziałów i stron między książkami. Opcjonalnie reguła automatycznego sortowania może być ustawiona, aby automatycznie sortować zawartość tej książki po jej zmianach.', 'books_sort_auto_sort' => 'Opcja automatycznego sortowania', 'books_sort_auto_sort_active' => 'Automatyczne sortowanie aktywne: :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_auto_sort_creation_hint' => 'Reguły opcji automatycznego sortowania mogą być tworzone w obszarze ustawień "Listy i sortowanie" przez użytkownika z odpowiednimi uprawnieniami.', 'books_sort_named' => 'Sortuj książkę :bookName', 'books_sort_name' => 'Sortuj według nazwy', 'books_sort_created' => 'Sortuj według daty utworzenia', @@ -331,9 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Przełącz pasek boczny', - '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_contents' => 'Zawartość strony', + 'page_contents_none' => 'Nie znaleziono nagłówków w treści strony.', + 'page_contents_info' => 'Menu zawartości jest generowane z dowolnych formatów nagłówków używanych na stronie.', 'page_tags' => 'Tagi strony', 'chapter_tags' => 'Tagi rozdziału', 'book_tags' => 'Tagi książki', diff --git a/lang/pl/errors.php b/lang/pl/errors.php index 244913cac42..3cba1bfb24e 100644 --- a/lang/pl/errors.php +++ b/lang/pl/errors.php @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => 'Podany sekret dla tego API jest nieprawidłowy', 'api_user_no_api_permission' => 'Właściciel używanego tokenu API nie ma uprawnień do wykonywania zapytań do API', 'api_user_token_expired' => 'Token uwierzytelniania wygasł', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Tylko żądania GET są dozwolone podczas korzystania z API z uwierzytelniania opartego na plikach cookie', // Settings & Maintenance 'maintenance_test_email_failure' => 'Błąd podczas wysyłania testowej wiadomości e-mail:', diff --git a/lang/pl/settings.php b/lang/pl/settings.php index bfa6339d551..3658ba8fdcb 100644 --- a/lang/pl/settings.php +++ b/lang/pl/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Rozdziały na początku', 'sort_rule_op_chapters_last' => 'Rozdziały na końcu', 'sorting_page_limits' => 'Limity wyświetlania per strona', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sorting_page_limits_desc' => 'Ustaw ile elementów pokazywać na stronie w różnych listach w systemie. Zazwyczaj mniejsza ilość będzie bardziej wydajna, podczas gdy większa ilość unika konieczności kliknięcia na wiele stron. Zaleca się stosowanie wielokrotności 6 razy.', // Maintenance settings 'maint' => 'Konserwacja', @@ -207,7 +207,7 @@ 'role_all' => 'Wszyscy', 'role_own' => 'Własne', 'role_controlled_by_asset' => 'Kontrolowane przez zasób, do którego zostały udostępnione', - 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', + 'role_controlled_by_page_delete' => 'Kontrolowane przez uprawnienia do usuwania stron', 'role_save' => 'Zapisz rolę', 'role_users' => 'Użytkownicy w tej roli', 'role_users_none' => 'Brak użytkowników zapisanych do tej roli', @@ -264,9 +264,9 @@ 'users_mfa_desc' => 'Skonfiguruj uwierzytelnianie wieloskładnikowe jako dodatkową warstwę bezpieczeństwa dla swojego konta użytkownika.', 'users_mfa_x_methods' => ':count metoda skonfigurowana|:count metody skonfigurowane', 'users_mfa_configure' => 'Konfiguruj metody', - '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?', + 'users_mfa_reset' => 'Resetuj metody uwierzytelniania wieloetapowego', + 'users_mfa_reset_desc' => 'Spowoduje to zresetowanie i wyczyszczenie wszystkich skonfigurowanych metod uwierzytelniania wieloetapowego dla tego użytkownika. Jeśli uwierzytelnianie wieloetapowe jest wymagane przez dowolną z ich ról, zostaną poproszone o skonfigurowanie nowych metod przy następnym logowaniu.', + 'users_mfa_reset_confirm' => 'Czy na pewno chcesz zresetować uwierzytelnianie wieloskładnikowe dla tego użytkownika?', // API Tokens 'user_api_token_create' => 'Utwórz klucz API', @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/pt/activities.php b/lang/pt/activities.php index e2ed42c7111..b7ca3d5b474 100644 --- a/lang/pt/activities.php +++ b/lang/pt/activities.php @@ -99,8 +99,8 @@ 'user_update_notification' => 'Utilizador atualizado com sucesso', 'user_delete' => 'utilizador eliminado', 'user_delete_notification' => 'Utilizador removido com sucesso', - 'user_mfa_reset' => 'reset MFA for user', - 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', + 'user_mfa_reset' => 'reiniciar a MFA para o utilizador', + 'user_mfa_reset_notification' => 'Reinicialização dos métodos de autenticação multifatorial', // API Tokens 'api_token_create' => 'token API criado', diff --git a/lang/pt/auth.php b/lang/pt/auth.php index 453b201689d..cf662b42af8 100644 --- a/lang/pt/auth.php +++ b/lang/pt/auth.php @@ -8,7 +8,7 @@ 'failed' => 'Estas credenciais não coincidem com os nossos registos.', 'throttle' => 'Demasiadas tentativas de acesso. Tente novamente em :seconds segundos.', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Foram efetuadas demasiadas tentativas de verificação multifatorial. Por favor, tente novamente daqui a :seconds segundos.', // Login & Register 'sign_up' => 'Registar', diff --git a/lang/pt/editor.php b/lang/pt/editor.php index e3069909c67..2a5a0f5c6e3 100644 --- a/lang/pt/editor.php +++ b/lang/pt/editor.php @@ -48,7 +48,7 @@ 'superscript' => 'Superior à linha', 'subscript' => 'Inferior à linha', 'text_color' => 'Cor do texto', - 'highlight_color' => 'Highlight color', + 'highlight_color' => 'Cor de destaque', 'custom_color' => 'Cor personalizada', 'remove_color' => 'Remover cor', 'background_color' => 'Cor de fundo', @@ -83,9 +83,9 @@ 'table_properties' => 'Propriedades da tabela', 'table_properties_title' => 'Propriedades da Tabela', 'delete_table' => 'Eliminar tabela', - 'table_clear_formatting' => 'Clear table formatting', - 'resize_to_contents' => 'Resize to contents', - 'row_header' => 'Row header', + 'table_clear_formatting' => 'Limpar formatação de tabela', + 'resize_to_contents' => 'Redimensionar para o conteúdo', + 'row_header' => 'Cabeçalho da linha', 'insert_row_before' => 'Inserir linha antes', 'insert_row_after' => 'Inserir linha depois', 'delete_row' => 'Eliminar linha', @@ -149,7 +149,7 @@ 'url' => 'URL', 'text_to_display' => 'Texto a ser exibido', 'title' => 'Título', - 'browse_links' => 'Browse links', + 'browse_links' => 'Procurar ligações', 'open_link' => 'Abrir ligação', 'open_link_in' => 'Abrir ligação em...', 'open_link_current' => 'Janela atual', @@ -167,7 +167,7 @@ 'about_title' => 'Sobre o Editor WYSIWYG', 'editor_license' => 'Editor da licença de direitos autorais', 'editor_lexical_license' => 'Este editor é criado como um fork do :lexicaLink que é distribuído sob a licença MIT.', - 'editor_lexical_license_link' => 'Full license details can be found here.', + 'editor_lexical_license_link' => 'Detalhes da licença completa podem ser encontrados aqui.', 'editor_tiny_license' => 'Este editor foi criado com :tinyLink que é fornecido sob a licença MIT.', 'editor_tiny_license_link' => 'Os dados relativos aos direitos de autor e à licença do TinyMCE podem ser encontrados aqui.', 'save_continue' => 'Salvar página e continuar', diff --git a/lang/pt/entities.php b/lang/pt/entities.php index c278796a43f..5038eebcc7a 100644 --- a/lang/pt/entities.php +++ b/lang/pt/entities.php @@ -46,27 +46,27 @@ 'import' => 'Importar', 'import_validate' => 'Validar Importação', 'import_desc' => 'Importar livros, capítulos e páginas usando uma exportação ZIP portátil da mesma ou uma instância diferente. Selecione um arquivo ZIP para prosseguir. Após o carregamento e validação do arquivo, conseguirá configurar e confirmar a importação na próxima visualização.', - 'import_zip_select' => 'Select ZIP file to upload', - 'import_zip_validation_errors' => 'Errors were detected while validating the provided ZIP file:', - 'import_pending' => 'Pending Imports', - 'import_pending_none' => 'No imports have been started.', + 'import_zip_select' => 'Selecione o ficheiro ZIP para enviar', + 'import_zip_validation_errors' => 'Foram detetados erros ao validar o ficheiro ZIP fornecido:', + 'import_pending' => 'Aguardando importação', + 'import_pending_none' => 'Nenhuma importação foi iniciada.', 'import_continue' => 'Continuar importação', - 'import_continue_desc' => 'Continuar importação', - 'import_details' => 'Import Details', - 'import_run' => 'Run Import', - 'import_size' => ':size Import ZIP Size', - 'import_uploaded_at' => 'Uploaded :relativeTime', - 'import_uploaded_by' => 'Uploaded by', - 'import_location' => 'Import Location', - 'import_location_desc' => 'Select a target location for your imported content. You\'ll need the relevant permissions to create within the location you choose.', - 'import_delete_confirm' => 'Are you sure you want to delete this import?', + 'import_continue_desc' => 'Verifique o conteúdo a importar a partir do ficheiro ZIP carregado. Quando estiver pronto, execute a importação para adicionar o seu conteúdo a este sistema. O ficheiro ZIP de importação carregado será automaticamente removido após a importação bem-sucedida.', + 'import_details' => 'Detalhes da importação', + 'import_run' => 'Executar Importação', + 'import_size' => ':size Tamanho do ZIP importado', + 'import_uploaded_at' => 'Carregado :relativeTime', + 'import_uploaded_by' => 'Carregado por', + 'import_location' => 'Local de Importação', + 'import_location_desc' => 'Selecione um local de destino para o seu conteúdo importado. Terá de dispor das permissões necessárias para criar conteúdo no local que escolher.', + 'import_delete_confirm' => 'Tem a certeza que pretende eliminar a importação?', 'import_delete_desc' => 'Isto irá eliminar o arquivo ZIP de importação enviado e não pode ser desfeito.', - 'import_errors' => 'Import Errors', - 'import_errors_desc' => 'The follow errors occurred during the import attempt:', - 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', - 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter', - 'breadcrumb_siblings_for_book' => 'Navigate siblings for book', - 'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf', + 'import_errors' => 'Erros de Importação', + 'import_errors_desc' => 'Ocorreram os seguintes erros durante a tentativa de importação:', + 'breadcrumb_siblings_for_page' => 'Navegar itens do mesmo nível por página', + 'breadcrumb_siblings_for_chapter' => 'Navegar itens do mesmo nível por capítulo', + 'breadcrumb_siblings_for_book' => 'Navegar itens do mesmo nível por livro', + 'breadcrumb_siblings_for_bookshelf' => 'Navegar itens do mesmo nível por estante', // Permissions and restrictions 'permissions' => 'Permissões', @@ -172,8 +172,8 @@ 'books_sort' => 'Ordenar Conteúdos do Livro', 'books_sort_desc' => 'Mova capítulos e páginas de um livro para reorganizar o seu conteúdo. É possível acrescentar outros livros, o que permite uma movimentação fácil de capítulos e páginas entre livros. Opcionalmente, uma regra de organização automática pode ser definida para classificar automaticamente o conteúdo deste livro após alterações.', 'books_sort_auto_sort' => '', - '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_auto_sort_active' => 'Ordenação automática ativada: :sortName', + 'books_sort_auto_sort_creation_hint' => 'As regras da opção de ordenação automática podem ser criadas na área de configurações "Listas e Ordenação" por um utilizador com as permissões necessárias.', 'books_sort_named' => 'Ordenar Livro :bookName', 'books_sort_name' => 'Ordenar por Nome', 'books_sort_created' => 'Ordenar por Data de Criação', @@ -235,7 +235,7 @@ 'pages_delete_draft' => 'Eliminar Rascunho de Página', 'pages_delete_success' => 'Página eliminada', 'pages_delete_draft_success' => 'Rascunho de página eliminado', - 'pages_delete_warning_template' => 'This page is in active use as a book or chapter default page template. These books or chapters will no longer have a default page template assigned after this page is deleted.', + 'pages_delete_warning_template' => 'Esta página é atualmente utilizada como modelo de página predefinido para livros ou capítulos. Após a eliminação desta página, estes livros ou capítulos deixarão de ter um modelo de página predefinido atribuído.', 'pages_delete_confirm' => 'Tem certeza que deseja eliminar a página?', 'pages_delete_draft_confirm' => 'Tem certeza que deseja eliminar o rascunho de página?', 'pages_editing_named' => 'A Editar a Página :pageName', @@ -252,8 +252,8 @@ 'pages_edit_switch_to_markdown_clean' => '(Conteúdo Limitado)', 'pages_edit_switch_to_markdown_stable' => '(Conteúdo Estável)', 'pages_edit_switch_to_wysiwyg' => 'Alternar para o editor WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg' => 'Switch to new WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg' => 'Mudar para o novo WYSIWYG', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(Em fase de testes beta)', 'pages_edit_set_changelog' => 'Relatar Alterações', 'pages_edit_enter_changelog_desc' => 'Digite uma breve descrição das alterações efetuadas por si', 'pages_edit_enter_changelog' => 'Inserir Alterações', @@ -273,7 +273,7 @@ 'pages_md_insert_drawing' => 'Inserir Desenho', 'pages_md_show_preview' => 'Mostrar pré-visualização', 'pages_md_sync_scroll' => 'Sincronizar pré-visualização', - 'pages_md_plain_editor' => 'Plaintext editor', + 'pages_md_plain_editor' => 'Editor de texto simples', 'pages_drawing_unsaved' => 'Encontrado um rascunho não guardado', 'pages_drawing_unsaved_confirm' => 'Dados de um rascunho não guardado foi encontrado de um tentativa anteriormente falhada. Deseja restaurar e continuar a edição desse rascunho?', 'pages_not_in_chapter' => 'A página não está dentro de um capítulo', @@ -331,9 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Alternar 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_contents' => 'Conteúdo da Página', + 'page_contents_none' => 'Não foram encontrados títulos no conteúdo da página.', + 'page_contents_info' => 'O índice é gerado a partir dos formatos de título utilizados na página.', 'page_tags' => 'Etiquetas de Página', 'chapter_tags' => 'Etiquetas do Capítulo', 'book_tags' => 'Etiquetas do Livro', @@ -401,11 +401,11 @@ 'comment' => 'Comentário', 'comments' => 'Comentários', 'comment_add' => 'Adicionar Comentário', - 'comment_none' => 'No comments to display', + 'comment_none' => 'Não há comentários para apresentar', 'comment_placeholder' => 'Digite aqui os seus comentários', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', + 'comment_thread_count' => ':count Tópico de comentários|:count Tópicos de comentários', + 'comment_archived_count' => ':count Arquivado', + 'comment_archived_threads' => 'Tópicos Arquivados', 'comment_save' => 'Guardar comentário', 'comment_new' => 'Comentário Novo', 'comment_created' => 'comentado :createDiff', @@ -414,14 +414,14 @@ 'comment_deleted_success' => 'Comentário removido', 'comment_created_success' => 'Comentário adicionado', 'comment_updated_success' => 'Comentário editado', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', + 'comment_archive_success' => 'Comentário arquivado', + 'comment_unarchive_success' => 'Comentário não arquivado', + 'comment_view' => 'Ver comentário', + 'comment_jump_to_thread' => 'Ir para o tópico', 'comment_delete_confirm' => 'Tem a certeza de que deseja eliminar este comentário?', 'comment_in_reply_to' => 'Em resposta à :commentId', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', + 'comment_reference' => 'Referência', + 'comment_reference_outdated' => '(Desatualizado)', 'comment_editor_explain' => 'Aqui estão os comentários que foram deixados nesta página. Comentários podem ser adicionados e geridos ao visualizar a página guardada.', // Revision @@ -452,7 +452,7 @@ // References 'references' => 'Referências', 'references_none' => 'Não há referências registadas para este item.', - 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', + 'references_to_desc' => 'A seguir, encontra-se uma lista de todo o conteúdo conhecido no sistema associado a este item.', // Watch Options 'watch' => 'Ver', @@ -470,11 +470,11 @@ 'watch_desc_comments_page' => 'Notificar sobre alterações na página e novos comentários.', 'watch_change_default' => 'Alterar preferências padrão de notificação', 'watch_detail_ignore' => 'Ignorar notificações', - 'watch_detail_new' => 'Watching for new pages', - 'watch_detail_updates' => 'Watching new pages and updates', - 'watch_detail_comments' => 'Watching new pages, updates & comments', - 'watch_detail_parent_book' => 'Watching via parent book', + 'watch_detail_new' => 'A observar novas páginas', + 'watch_detail_updates' => 'A observar novas páginas e atualizações', + 'watch_detail_comments' => 'A observar novas páginas, atualizações e comentários', + 'watch_detail_parent_book' => 'A observar via livro pai', 'watch_detail_parent_book_ignore' => 'A ignorar através do livro pai', - 'watch_detail_parent_chapter' => 'Watching via parent chapter', - 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', + 'watch_detail_parent_chapter' => 'A observar via capítulo pai', + 'watch_detail_parent_chapter_ignore' => 'A ignorar via capítulo pai', ]; diff --git a/lang/pt/errors.php b/lang/pt/errors.php index e32257e6240..d6e92290cd4 100644 --- a/lang/pt/errors.php +++ b/lang/pt/errors.php @@ -10,7 +10,7 @@ // Auth 'error_user_exists_different_creds' => 'Um utilizador com o endereço de e-mail :email já existe mas com credenciais diferentes.', - 'auth_pre_register_theme_prevention' => 'User account could not be registered for the provided details', + 'auth_pre_register_theme_prevention' => 'Não foi possível registar a conta de utilizador com os detalhes fornecidos', 'email_already_confirmed' => 'E-mail já foi confirmado. Tente iniciar sessão.', 'email_confirmation_invalid' => 'Este token de confirmação não é válido ou já foi utilizado. Por favor, tente registar-se novamente.', 'email_confirmation_expired' => 'O token de confirmação já expirou. Um novo e-mail foi enviado.', @@ -37,7 +37,7 @@ 'social_driver_not_found' => 'Social driver não encontrado', 'social_driver_not_configured' => 'Os seus parâmetros sociais de :socialAccount não estão corretamente configurados.', 'invite_token_expired' => 'Este link de convite expirou. Alternativamente, pode tentar redefinir a senha da sua conta.', - 'login_user_not_found' => 'A user for this action could not be found.', + 'login_user_not_found' => 'Não foi possível encontrar um utilizador para esta ação.', // System 'path_not_writable' => 'O caminho do arquivo :filePath não pôde ser carregado. Certifique-se de que tem permissões de escrita no servidor.', @@ -51,9 +51,9 @@ 'image_upload_error' => 'Ocorreu um erro no carregamento da imagem', 'image_upload_type_error' => 'O tipo de imagem enviada é inválida', 'image_upload_replace_type' => 'A imagem de substituição deverá ser do mesmo tipo que a anterior', - 'image_upload_memory_limit' => 'Failed to handle image upload and/or create thumbnails due to system resource limits.', - 'image_thumbnail_memory_limit' => 'Failed to create image size variations due to system resource limits.', - 'image_gallery_thumbnail_memory_limit' => 'Failed to create gallery thumbnails due to system resource limits.', + 'image_upload_memory_limit' => 'Não foi possível processar o carregamento da imagem e/ou criar miniaturas devido a limites de recursos do sistema.', + 'image_thumbnail_memory_limit' => 'Não foi possível criar variações de tamanho de imagem devido a limites de recursos do sistema.', + 'image_gallery_thumbnail_memory_limit' => 'Não foi possível criar miniaturas da galeria devido a limites de recursos do sistema.', 'drawing_data_not_found' => 'Dados de desenho não puderam ser carregados. Talvez o arquivo de desenho não exista mais ou não tenha permissão para aceder-lhe.', // Attachments @@ -107,16 +107,16 @@ // Import 'import_zip_cant_read' => 'Não foi possível ler o ficheiro ZIP.', - 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', - 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', - 'import_validation_failed' => 'Import ZIP failed to validate with errors:', - 'import_zip_failed_notification' => 'Failed to import ZIP file.', - 'import_perms_books' => 'You are lacking the required permissions to create books.', - 'import_perms_chapters' => 'You are lacking the required permissions to create chapters.', - 'import_perms_pages' => 'You are lacking the required permissions to create pages.', - 'import_perms_images' => 'You are lacking the required permissions to create images.', - 'import_perms_attachments' => 'You are lacking the required permission to create attachments.', + 'import_zip_cant_decode_data' => 'Não foi possível encontrar nem descodificar o conteúdo do ficheiro ZIP data.json.', + 'import_zip_no_data' => 'Os dados do ficheiro ZIP não contêm o conteúdo esperado de livro, capítulo ou página.', + 'import_zip_data_too_large' => 'O conteúdo do ficheiro ZIP data.json excede o tamanho máximo de upload definido para a aplicação.', + 'import_validation_failed' => 'A importação do ficheiro ZIP não foi validada devido a erros:', + 'import_zip_failed_notification' => 'Não foi possível importar o ficheiro ZIP.', + 'import_perms_books' => 'Não dispõe das permissões necessárias para criar livros.', + 'import_perms_chapters' => 'Não dispõe das permissões necessárias para criar capítulos.', + 'import_perms_pages' => 'Não dispõe das permissões necessárias para criar páginas.', + 'import_perms_images' => 'Não dispõe das permissões necessárias para criar imagens.', + 'import_perms_attachments' => 'Não dispõe das permissões necessárias para criar anexos.', // API errors 'api_no_authorization_found' => 'Nenhum token de autorização encontrado na requisição', @@ -125,11 +125,11 @@ 'api_incorrect_token_secret' => 'O segredo fornecido para o token de API usado está incorreto', 'api_user_no_api_permission' => 'O proprietário do token de API utilizado não tem permissão para fazer requisições de API', 'api_user_token_expired' => 'O token de autenticação expirou', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Ao utilizar a API com autenticação baseada em “cookies”, apenas são permitidos pedidos GET', // Settings & Maintenance 'maintenance_test_email_failure' => 'Erro lançado ao enviar um e-mail de teste:', // HTTP errors - 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', + 'http_ssr_url_no_match' => 'O URL não corresponde aos "hosts" SSR permitidos configurados', ]; diff --git a/lang/pt/notifications.php b/lang/pt/notifications.php index cbe3a511c88..14beff712eb 100644 --- a/lang/pt/notifications.php +++ b/lang/pt/notifications.php @@ -11,11 +11,11 @@ 'updated_page_subject' => 'Página atualizada: :pageName', 'updated_page_intro' => 'Uma página foi atualizada em :appName:', 'updated_page_debounce' => 'Para evitar um grande volume de notificações, durante algum tempo não serão enviadas notificações de edições futuras para esta página através do mesmo editor.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'Foi mencionado num comentário na página: :pageName', + 'comment_mention_intro' => 'Foi mencionado num comentário no :appName:', 'detail_page_name' => 'Nome da Página:', - 'detail_page_path' => 'Page Path:', + 'detail_page_path' => 'Caminho da página:', 'detail_commenter' => 'Comentador:', 'detail_comment' => 'Comentário:', 'detail_created_by' => 'Criado Por:', diff --git a/lang/pt/preferences.php b/lang/pt/preferences.php index b7308aaf910..b98cfa3691a 100644 --- a/lang/pt/preferences.php +++ b/lang/pt/preferences.php @@ -23,7 +23,7 @@ 'notifications_desc' => 'Controlar as notificações via correio eletrónico quando certas atividades são executadas pelo sistema.', 'notifications_opt_own_page_changes' => 'Notificar quando páginas que possuo sofrem alterações', 'notifications_opt_own_page_comments' => 'Notificar quando comentam páginas que possuo', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', + 'notifications_opt_comment_mentions' => 'Notificar-me quando for mencionado num comentário', 'notifications_opt_comment_replies' => 'Notificar respostas aos meus comentários', 'notifications_save' => 'Guardar preferências', 'notifications_update_success' => 'Preferências de notificação foram atualizadas!', @@ -43,7 +43,7 @@ 'profile_email_no_permission' => 'Infelizmente você não tem permissão para alterar seu correio eletrônico. Se você quiser mudar isso, você precisa pedir a um administrador para alterar por você.', 'profile_avatar_desc' => 'Selecione uma imagem que será usada para lhe representar aos outros usuários do sistema. Idealmente, esta imagem deve ser quadrada e sobre 256px em largura e altura.', 'profile_admin_options' => 'Opções de administrador', - 'profile_admin_options_desc' => 'Additional administrator-level options, like those to manage role assignments, can be found for your user account in the "Settings > Users" area of the application.', + 'profile_admin_options_desc' => 'Poderá encontrar opções adicionais de nível de administrador, como as destinadas a gerir a atribuição de funções, na sua conta de utilizador, na secção "Definições > Utilizadores" da aplicação.', 'delete_account' => 'Excluir Conta', 'delete_my_account' => 'Excluir a Minha Conta', diff --git a/lang/pt/settings.php b/lang/pt/settings.php index 1488db87a73..7ec587d51e1 100644 --- a/lang/pt/settings.php +++ b/lang/pt/settings.php @@ -75,36 +75,36 @@ 'reg_confirm_restrict_domain_placeholder' => 'Nenhuma restrição definida', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', - 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', - 'sorting_rules' => 'Sort Rules', - 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', - 'sort_rule_assigned_to_x_books' => 'Assigned to :count Book|Assigned to :count Books', - 'sort_rule_create' => 'Create Sort Rule', - 'sort_rule_edit' => 'Edit Sort Rule', - 'sort_rule_delete' => 'Delete Sort Rule', - 'sort_rule_delete_desc' => 'Remove this sort rule from the system. Books using this sort will revert to manual sorting.', - 'sort_rule_delete_warn_books' => 'This sort rule is currently used on :count book(s). Are you sure you want to delete this?', - 'sort_rule_delete_warn_default' => 'This sort rule is currently used as the default for books. Are you sure you want to delete this?', - 'sort_rule_details' => 'Sort Rule Details', - 'sort_rule_details_desc' => 'Set a name for this sort rule, which will appear in lists when users are selecting a sort.', - 'sort_rule_operations' => 'Sort Operations', - 'sort_rule_operations_desc' => 'Configure the sort actions to be performed by moving them from the list of available operations. Upon use, the operations will be applied in order, from top to bottom. Any changes made here will be applied to all assigned books upon save.', - 'sort_rule_available_operations' => 'Available Operations', - 'sort_rule_available_operations_empty' => 'No operations remaining', - 'sort_rule_configured_operations' => 'Configured Operations', - 'sort_rule_configured_operations_empty' => 'Drag/add operations from the "Available Operations" list', + 'sorting' => 'Listas e ordenação', + 'sorting_book_default' => 'Regra de ordenação padrão dos livros', + 'sorting_book_default_desc' => 'Selecione a regra de ordenação predefinida a aplicar aos novos livros. Isto não afetará os livros existentes e pode ser substituído individualmente para cada livro.', + 'sorting_rules' => 'Regras de Ordenação', + 'sorting_rules_desc' => 'Trata-se de operações de ordenação predefinidas que podem ser aplicadas ao conteúdo do sistema.', + 'sort_rule_assigned_to_x_books' => 'Atribuído a: :count Livro|Atribuído a: :count Livros', + 'sort_rule_create' => 'Criar Regra de Ordenação', + 'sort_rule_edit' => 'Editar Regra de Ordenação', + 'sort_rule_delete' => 'Eliminar Regra de Ordenação', + 'sort_rule_delete_desc' => 'Remova esta regra de ordenação do sistema. Os livros que utilizam esta ordenação voltarão a ser ordenados manualmente.', + 'sort_rule_delete_warn_books' => 'Esta regra de ordenação é atualmente utilizada em :count livro(s). Tem a certeza de que deseja eliminar isto?', + 'sort_rule_delete_warn_default' => 'Esta regra de ordenação é atualmente utilizada em livros. Tem a certeza de que deseja eliminar isto?', + 'sort_rule_details' => 'Detalhes de Regras de Ordenação', + 'sort_rule_details_desc' => 'Defina um nome para esta regra de ordenação, que aparecerá nas listas quando os utilizadores selecionarem uma opção de ordenação.', + 'sort_rule_operations' => 'Operações de Ordenação', + 'sort_rule_operations_desc' => 'Configure as ações de ordenação a executar, selecionando-as na lista de operações disponíveis. Quando utilizadas, as operações serão aplicadas por ordem, de cima para baixo. Quaisquer alterações efetuadas aqui serão aplicadas a todos os livros atribuídos quando se guardar.', + 'sort_rule_available_operations' => 'Operações Disponíveis', + 'sort_rule_available_operations_empty' => 'Não há operações pendentes', + 'sort_rule_configured_operations' => 'Operações Configuradas', + 'sort_rule_configured_operations_empty' => 'Operações de arrastar/adicionar a partir da lista "Operações Disponíveis"', 'sort_rule_op_asc' => '(Asc)', 'sort_rule_op_desc' => '(Desc)', - 'sort_rule_op_name' => 'Name - Alphabetical', - 'sort_rule_op_name_numeric' => 'Name - Numeric', - 'sort_rule_op_created_date' => 'Created Date', - 'sort_rule_op_updated_date' => 'Updated Date', - 'sort_rule_op_chapters_first' => 'Chapters First', - 'sort_rule_op_chapters_last' => 'Chapters Last', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sort_rule_op_name' => 'Nome - Alfabético', + 'sort_rule_op_name_numeric' => 'Nome - Numérico', + 'sort_rule_op_created_date' => 'Data de criação', + 'sort_rule_op_updated_date' => 'Data de atualização', + 'sort_rule_op_chapters_first' => 'Capítulos: Primeiro', + 'sort_rule_op_chapters_last' => 'Capítulos: Últimos', + 'sorting_page_limits' => 'Limites de Exibição por Página', + 'sorting_page_limits_desc' => 'Defina o número de itens a apresentar por página nas várias listas do sistema. Normalmente, um número mais baixo proporciona melhor desempenho, enquanto um número mais elevado evita a necessidade de percorrer várias páginas. É recomendado utilizar um múltiplo de 6.', // Maintenance settings 'maint' => 'Manutenção', @@ -141,7 +141,7 @@ 'recycle_bin_contents_empty' => 'A reciclagem está atualmente vazia', 'recycle_bin_empty' => 'Esvaziar Reciclagem', 'recycle_bin_empty_confirm' => 'Isto irá destruir permanentemente todos os itens na reciclagem inclusive o conteúdo de cada item. Tem certeza de que a deseja esvaziar?', - 'recycle_bin_destroy_confirm' => 'This action will permanently delete this item from the system, along with any child elements listed below, and you will not be able to restore this content. Are you sure you want to permanently delete this item?', + 'recycle_bin_destroy_confirm' => 'Esta ação irá eliminar definitivamente este item do sistema, com quaisquer elementos secundários listados abaixo, e não será possível recuperar este conteúdo. Tem a certeza de que deseja eliminar definitivamente este item?', 'recycle_bin_destroy_list' => 'Itens a serem Destruídos', 'recycle_bin_restore_list' => 'Itens a serem Restaurados', 'recycle_bin_restore_confirm' => 'Esta ação irá restaurar o item excluído, inclusive quaisquer elementos filhos, para o seu local original. Se a localização original tiver, entretanto, sido eliminada e estiver agora na reciclagem, o item pai também precisará de ser restaurado.', @@ -194,20 +194,20 @@ 'role_access_api' => 'Aceder à API do sistema', 'role_manage_settings' => 'Gerir as configurações da aplicação', 'role_export_content' => 'Exportar conteúdo', - 'role_import_content' => 'Import content', + 'role_import_content' => 'Importar conteúdo', 'role_editor_change' => 'Alterar editor de página', - 'role_notifications' => 'Receive & manage notifications', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_notifications' => 'Receber e gerir notificações', + 'role_permission_note_users_and_roles' => 'Tecnicamente, estas permissões também permitirão visualizar e pesquisar utilizadores e papéis no sistema.', 'role_asset' => 'Permissões de Ativos', 'roles_system_warning' => 'Esteja ciente de que o acesso a qualquer uma das três permissões acima pode permitir que um utilizador altere os seus próprios privilégios ou privilégios de outros no sistema. Apenas atribua cargos com essas permissões a utilizadores de confiança.', 'role_asset_desc' => 'Estas permissões controlam o acesso padrão para os ativos dentro do sistema. Permissões em Livros, Capítulos e Páginas serão sobrescritas por estas permissões.', 'role_asset_admins' => 'Os administradores recebem automaticamente acesso a todo o conteúdo, mas estas opções podem mostrar ou ocultar as opções da Interface de Usuário.', 'role_asset_image_view_note' => 'Isto está relacionado com a visibilidade do gerenciador de imagens. O acesso real dos arquivos de imagem enviados dependerá da opção de armazenamento de imagens do sistema.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Tecnicamente, estas permissões também permitirão visualizar e pesquisar utilizadores no sistema.', 'role_all' => 'Todos', 'role_own' => 'Próprio', 'role_controlled_by_asset' => 'Controlado pelo ativo para o qual eles são enviados', - 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', + 'role_controlled_by_page_delete' => 'Controlado pelas permissões de eliminação de páginas', 'role_save' => 'Guardar Cargo', 'role_users' => 'Utilizadores com este cargo', 'role_users_none' => 'Nenhum utilizador está atualmente vinculado a este cargo', @@ -229,8 +229,8 @@ 'users_send_invite_text' => 'Pode escolher enviar a este utilizador um convite por e-mail que o possibilitará definir a sua própria palavra-passe, ou defina você mesmo uma.', 'users_send_invite_option' => 'Enviar convite por e-mail', 'users_external_auth_id' => 'ID de Autenticação Externa', - 'users_external_auth_id_desc' => 'When an external authentication system is in use (such as SAML2, OIDC or LDAP) this is the ID which links this BookStack user to the authentication system account. You can ignore this field if using the default email-based authentication.', - 'users_password_warning' => 'Only fill the below if you would like to change the password for this user.', + 'users_external_auth_id_desc' => 'Quando se utiliza um sistema de autenticação externo (como SAML2, OIDC ou LDAP), este é o ID que associa este utilizador do BookStack à conta do sistema de autenticação. Pode ignorar este campo se estiver a utilizar a autenticação padrão baseada no e-mail.', + 'users_password_warning' => 'Preencha os campos abaixo apenas se pretender alterar a palavra-passe deste utilizador.', 'users_system_public' => 'Este utilizador representa quaisquer convidados que visitam a aplicação. Não pode ser utilizado para efetuar autenticação, mas é automaticamente atribuído.', 'users_delete' => 'Eliminar Utilizador', 'users_delete_named' => 'Eliminar :userName', @@ -246,7 +246,7 @@ 'users_preferred_language' => 'Linguagem de Preferência', 'users_preferred_language_desc' => 'Esta opção irá alterar o idioma utilizado para a interface de utilizador da aplicação. Isto não afetará nenhum conteúdo criado por utilizadores.', 'users_social_accounts' => 'Contas Sociais', - 'users_social_accounts_desc' => 'View the status of the connected social accounts for this user. Social accounts can be used in addition to the primary authentication system for system access.', + 'users_social_accounts_desc' => 'Ver o estado das contas sociais associadas a este utilizador. As contas sociais podem ser utilizadas em complemento ao sistema de autenticação principal para aceder ao sistema.', 'users_social_accounts_info' => 'Aqui pode ligar outras contas para acesso mais rápido. Desligar uma conta não retira a possibilidade de acesso usando-a. Para revogar o acesso ao perfil através da conta social, você deverá fazê-lo na sua conta social.', 'users_social_connect' => 'Contas Associadas', 'users_social_disconnect' => 'Dissociar Conta', @@ -255,7 +255,7 @@ 'users_social_connected' => 'A conta:socialAccount foi associada com sucesso ao seu perfil.', 'users_social_disconnected' => 'A conta:socialAccount foi dissociada com sucesso de seu perfil.', 'users_api_tokens' => 'Tokens de API', - 'users_api_tokens_desc' => 'Create and manage the access tokens used to authenticate with the BookStack REST API. Permissions for the API are managed via the user that the token belongs to.', + 'users_api_tokens_desc' => 'Crie e faça a gestão dos tokens de acesso utilizados para autenticação na API REST do BookStack. As permissões para a API são geridas através do utilizador a quem o token pertence.', 'users_api_tokens_none' => 'Nenhum token de API foi criado para este utilizador', 'users_api_tokens_create' => 'Criar Token', 'users_api_tokens_expires' => 'Expira', @@ -264,9 +264,9 @@ 'users_mfa_desc' => 'Configure a autenticação multi-fatores como uma camada extra de segurança para sua conta de utilizador.', 'users_mfa_x_methods' => ':count método configurado|:count métodos configurados', 'users_mfa_configure' => 'Configurar Métodos', - '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?', + 'users_mfa_reset' => 'Redefinir métodos de autenticação multifator', + 'users_mfa_reset_desc' => 'Isto irá reiniciar e eliminar todos os métodos de autenticação multifator configurados para este utilizador. Se a autenticação multifator for exigida por alguma das suas funções, ser-lhe-á solicitado que configure novos métodos no seu próximo início de sessão.', + 'users_mfa_reset_confirm' => 'Tem a certeza de que deseja repor a autenticação multifator para este utilizador?', // API Tokens 'user_api_token_create' => 'Criar Token de API', @@ -316,13 +316,13 @@ 'webhooks_last_error_message' => 'Última mensagem de erro:', // Licensing - 'licenses' => 'Licenses', - 'licenses_desc' => 'This page details license information for BookStack in addition to the projects & libraries that are used within BookStack. Many projects listed may only be used in a development context.', - 'licenses_bookstack' => 'BookStack License', - 'licenses_php' => 'PHP Library Licenses', - 'licenses_js' => 'JavaScript Library Licenses', - 'licenses_other' => 'Other Licenses', - 'license_details' => 'License Details', + 'licenses' => 'Licenças', + 'licenses_desc' => 'Esta página apresenta informações sobre as licenças do BookStack, bem como sobre os projetos e bibliotecas utilizados no BookStack. Muitos dos projetos aqui listados só podem ser utilizados num contexto de desenvolvimento.', + 'licenses_bookstack' => 'Licença de BookStack', + 'licenses_php' => 'Licenças de Bibliotecas PHP', + 'licenses_js' => 'Licenças de Bibliotecas de JavaScript', + 'licenses_other' => 'Outras Licenças', + 'license_details' => 'Detalhes de Licença', //! If editing translations files directly please ignore this in all //! languages apart from en. Content will be auto-copied from en. @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/pt/validation.php b/lang/pt/validation.php index 17d891cdb16..4d80f85c422 100644 --- a/lang/pt/validation.php +++ b/lang/pt/validation.php @@ -105,11 +105,11 @@ 'url' => 'O formato da URL :attribute é inválido.', 'uploaded' => 'O arquivo não pôde ser carregado. O servidor pode não aceitar arquivos deste tamanho.', - 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', - 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', - 'zip_model_expected' => 'Data object expected but ":type" found.', - 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', + 'zip_file' => 'O :attribute deve referenciar um ficheiro dentro do ZIP.', + 'zip_file_size' => 'O ficheiro :attribute não deve exceder :size MB.', + 'zip_file_mime' => 'O :attribute deve referenciar um ficheiro do tipo :validTypes, encontrado em :foundType.', + 'zip_model_expected' => 'Era esperado um objeto de dados, mas foi encontrado “:type”.', + 'zip_unique' => 'O :attribute deve ser único para o tipo de objeto dentro do ficheiro ZIP.', // Custom validation lines 'custom' => [ diff --git a/lang/pt_BR/activities.php b/lang/pt_BR/activities.php index e9069564f50..dbadcf1cc22 100644 --- a/lang/pt_BR/activities.php +++ b/lang/pt_BR/activities.php @@ -99,8 +99,8 @@ 'user_update_notification' => 'Usuário atualizado com sucesso', 'user_delete' => 'usuário excluído', 'user_delete_notification' => 'Usuário removido com sucesso', - 'user_mfa_reset' => 'reset MFA for user', - 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', + 'user_mfa_reset' => 'redefinir MFA para usuário', + 'user_mfa_reset_notification' => 'Redefinir métodos de autenticação de múltiplos fatores', // API Tokens 'api_token_create' => 'token de API criado', diff --git a/lang/pt_BR/auth.php b/lang/pt_BR/auth.php index dfcc47da6f8..e3455e144f2 100644 --- a/lang/pt_BR/auth.php +++ b/lang/pt_BR/auth.php @@ -8,7 +8,7 @@ 'failed' => 'As credenciais fornecidas não puderam ser validadas em nossos registros.', 'throttle' => 'Muitas tentativas de login. Por favor, tente novamente em :seconds segundos.', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Muitas tentativas de verificação de multifatores. Por favor tente novamente em :seconds segundos.', // Login & Register 'sign_up' => 'Criar Conta', diff --git a/lang/pt_BR/entities.php b/lang/pt_BR/entities.php index 7fa0b50c1d1..fafdb80d5ff 100644 --- a/lang/pt_BR/entities.php +++ b/lang/pt_BR/entities.php @@ -219,7 +219,7 @@ 'chapters_permissions_active' => 'Permissões de Capítulo Ativas', 'chapters_permissions_success' => 'Permissões de Capítulo Atualizadas', 'chapters_search_this' => 'Pesquisar neste Capítulo', - 'chapter_sort_book' => 'Classificar livro', + 'chapter_sort_book' => 'Ordenar livro', // Pages 'page' => 'Página', @@ -331,9 +331,9 @@ // 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_contents' => 'Conteúdos da página', + 'page_contents_none' => 'Nenhum título foi encontrado no conteúdo da página.', + 'page_contents_info' => 'O menu de conteúdo é gerado a partir de qualquer formato de cabeçalho usado na página.', 'page_tags' => 'Marcadores de Página', 'chapter_tags' => 'Marcadores de Capítulo', 'book_tags' => 'Marcadores de Livro', diff --git a/lang/pt_BR/settings.php b/lang/pt_BR/settings.php index 8129637c8dc..09832d8656b 100644 --- a/lang/pt_BR/settings.php +++ b/lang/pt_BR/settings.php @@ -183,7 +183,7 @@ 'role_details' => 'Detalhes do Perfil', 'role_name' => 'Nome do Perfil', 'role_desc' => 'Breve Descrição do Perfil', - 'role_mfa_enforced' => 'Requer Autenticação Multi-fator', + 'role_mfa_enforced' => 'Requer Autenticação Multifator', 'role_external_auth_id' => 'IDs de Autenticação Externa', 'role_system' => 'Permissões do Sistema', 'role_manage_users' => 'Gerenciar usuários', @@ -260,13 +260,13 @@ 'users_api_tokens_create' => 'Criar Token', 'users_api_tokens_expires' => 'Expira', 'users_api_tokens_docs' => 'Documentação da API', - 'users_mfa' => 'Autenticação de Múltiplos Fatores', - 'users_mfa_desc' => 'A autenticação multi-fator adiciona outra camada de segurança à sua conta.', + 'users_mfa' => 'Autenticação Multifator', + 'users_mfa_desc' => 'A autenticação multifator adiciona uma camada extra de segurança à sua conta.', 'users_mfa_x_methods' => ':count método configurado|:count métodos configurados', 'users_mfa_configure' => 'Configurar Métodos', - '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?', + 'users_mfa_reset' => 'Redefiner Métodos de Autenticação Multifator', + 'users_mfa_reset_desc' => 'Isto irá redefinir e limpar todos os métodos de autenticação multifator configurados para este usuário. Se a autenticação multifator for exigida por qualquer uma de suas funções, você será solicitado a configurar novos métodos em seu próximo login.', + 'users_mfa_reset_confirm' => 'Você tem certeza que deseja remover o método de autenticação multifator?', // API Tokens 'user_api_token_create' => 'Criar Token de API', @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/ro/settings.php b/lang/ro/settings.php index bbbaa34d959..7423c9822b9 100644 --- a/lang/ro/settings.php +++ b/lang/ro/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/ru/settings.php b/lang/ru/settings.php index 4fdd5784ab2..f3a6529ccc2 100644 --- a/lang/ru/settings.php +++ b/lang/ru/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/sk/settings.php b/lang/sk/settings.php index e76fefb98e8..52547235326 100644 --- a/lang/sk/settings.php +++ b/lang/sk/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/sl/settings.php b/lang/sl/settings.php index 9bd63a0e88e..71f280ce226 100644 --- a/lang/sl/settings.php +++ b/lang/sl/settings.php @@ -367,6 +367,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/sq/settings.php b/lang/sq/settings.php index d03024a89d6..0e5ce84cf21 100644 --- a/lang/sq/settings.php +++ b/lang/sq/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/sr/activities.php b/lang/sr/activities.php index 555d01adca1..bf6573e9718 100644 --- a/lang/sr/activities.php +++ b/lang/sr/activities.php @@ -77,20 +77,20 @@ 'maintenance_action_run' => 'покренуо акцију одржавања', // Webhooks - 'webhook_create' => 'креиран вебхоок', - 'webhook_create_notification' => 'Вебхоок је успешно креиран', - 'webhook_update' => 'ажуриран вебхоок', - 'webhook_update_notification' => 'Вебхоок је успешно ажуриран', - 'webhook_delete' => 'обрисан вебхоок', - 'webhook_delete_notification' => 'Вебхоок је успешно обрисан', + 'webhook_create' => 'креирана веб закачка', + 'webhook_create_notification' => 'Веб закачка је успешно креирана', + 'webhook_update' => 'ажурирана веб закачка', + 'webhook_update_notification' => 'Веб закачка је успешно ажурирана', + 'webhook_delete' => 'обрисана веб закачка', + 'webhook_delete_notification' => 'Веб закачка је успешно обрисана', // Imports 'import_create' => 'креиран увоз', - 'import_create_notification' => 'Import successfully uploaded', + 'import_create_notification' => 'Увоз је успешно отпремљен', 'import_run' => 'ажуриран увоз', - 'import_run_notification' => 'Content successfully imported', - 'import_delete' => 'deleted import', - 'import_delete_notification' => 'Import successfully deleted', + 'import_run_notification' => 'Садржај је успешно увезен', + 'import_delete' => 'обрисан увоз', + 'import_delete_notification' => 'Увоз је успешно обрисан', // Users 'user_create' => 'креирао корисника', @@ -99,8 +99,8 @@ 'user_update_notification' => 'Корисник је успешно ажуриран', 'user_delete' => 'избрисан корисника', 'user_delete_notification' => 'Корисник је успешно уклоњен', - 'user_mfa_reset' => 'reset MFA for user', - 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', + 'user_mfa_reset' => 'поништи МФА за корисника', + 'user_mfa_reset_notification' => 'Поништавања начина мултифакторске аутентификације', // API Tokens 'api_token_create' => 'креирао апи токен', @@ -130,12 +130,12 @@ 'comment_delete' => 'обрисан коментар', // Sort Rules - 'sort_rule_create' => 'created sort rule', - 'sort_rule_create_notification' => 'Sort rule successfully created', - 'sort_rule_update' => 'updated sort rule', - 'sort_rule_update_notification' => 'Sort rule successfully updated', - 'sort_rule_delete' => 'deleted sort rule', - 'sort_rule_delete_notification' => 'Sort rule successfully deleted', + 'sort_rule_create' => 'направљено је правило слагања', + 'sort_rule_create_notification' => 'Правило слагања је успешно направљено', + 'sort_rule_update' => 'ажурирано је правило слагања', + 'sort_rule_update_notification' => 'Правило слагања је успешно ажурирано', + 'sort_rule_delete' => 'избрисано је правило слагања', + 'sort_rule_delete_notification' => 'Правило слагања је успешно избрисано', // Other 'permissions_update' => 'ажуриране дозволе', diff --git a/lang/sr/auth.php b/lang/sr/auth.php index 96169fe2bab..78b3c76e2de 100644 --- a/lang/sr/auth.php +++ b/lang/sr/auth.php @@ -8,7 +8,7 @@ 'failed' => 'Ови акредитиви се не поклапају са нашом евиденцијом.', 'throttle' => 'Превише покушаја пријаве. Покушајте поново за :seconds секунди.', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Превише покушаја потврђивања вишефакторском аутентификацијом. Молим вас покушајте поново за :seconds секунди.', // Login & Register 'sign_up' => 'Региструј се', @@ -89,13 +89,13 @@ 'mfa_setup_remove_confirmation' => 'Да ли сте сигурни да желите да уклоните овај метод вишефакторске аутентификације?', 'mfa_setup_action' => 'Подешавање', 'mfa_backup_codes_usage_limit_warning' => 'Преостало вам је мање од 5 резервних кодова. Генеришите и сачувајте нови сет пре него што вам понестане кодова како бисте спречили да останете без налога.', - 'mfa_option_totp_title' => 'Aplikacije za mobilne uređaje', + 'mfa_option_totp_title' => 'Апликација за мобилне уређаје', 'mfa_option_totp_desc' => 'Да бисте користили вишефакторску аутентификацију, биће вам потребна мобилна апликација која подржава ТОТП, као што јеGoogle Authenticator, Authy или Microsoft Authenticator.', 'mfa_option_backup_codes_title' => 'Резервни кодови', 'mfa_option_backup_codes_desc' => 'Генерише скуп резервних кодова за једнократну употребу које ћете унети приликом пријављивања да бисте потврдили свој идентитет. Обавезно их чувајте на безбедном и безбедном месту.', 'mfa_gen_confirm_and_enable' => 'Потврдите и омогућите', 'mfa_gen_backup_codes_title' => 'Подешавање резервних кодова', - 'mfa_gen_backup_codes_desc' => 'Чувајте доњу листу кодова на безбедном месту. Када приступате систему, моћи ћете да користите један од кодова као други механизам за аутентификацију.', + 'mfa_gen_backup_codes_desc' => 'Сачувајте списак кодова испод на безбедном месту. Када приступате систему, моћи ћете да користите један од кодова као други механизам за аутентификацију.', 'mfa_gen_backup_codes_download' => 'Преузми кодове', 'mfa_gen_backup_codes_usage_warning' => 'Сваки код се може искористити једном', 'mfa_gen_totp_title' => 'Подешавање мобилне апликације', diff --git a/lang/sr/common.php b/lang/sr/common.php index c5c62db6588..a1efbb7af6c 100644 --- a/lang/sr/common.php +++ b/lang/sr/common.php @@ -12,7 +12,7 @@ 'save' => 'Сачувај', 'continue' => 'Настави', 'select' => 'Изабери', - 'toggle_all' => 'Сакриј/Прикажи све', + 'toggle_all' => 'Укључи све/ништа', 'more' => 'Више', // Form Labels @@ -20,18 +20,18 @@ 'description' => 'Опис', 'role' => 'Улога', 'cover_image' => 'Насловна слика', - 'cover_image_description' => 'Ова слика би требало да буде приближно 440к250px иако ће бити флексибилно скалирана и исечена како би одговарала корисничком интерфејсу у различитим сценаријима по потреби, тако да ће се стварне димензије приказа разликовати.', + 'cover_image_description' => 'Ова слика би требало да буде приближно 440х250px иако ће бити флексибилно скалирана и исечена како би одговарала корисничком интерфејсу у различитим сценаријима по потреби, тако да ће се стварне димензије приказа разликовати.', // Actions 'actions' => 'Радње', - 'view' => 'Преглед', + 'view' => 'Прегледај', 'view_all' => 'Прикажи све', 'new' => 'Ново', 'create' => 'Креирај', - 'update' => 'Ажурирање', + 'update' => 'Ажурирај', 'edit' => 'Уреди', 'archive' => 'Архивирај', - 'unarchive' => 'Un-Archive', + 'unarchive' => 'Деархивирај', 'sort' => 'Разврстај', 'move' => 'Премести', 'copy' => 'Умножи', @@ -40,7 +40,7 @@ 'delete_confirm' => 'Потврди брисање', 'search' => 'Претражи', 'search_clear' => 'Обриши претрагу', - 'reset' => 'Ресетуј', + 'reset' => 'Поништи', 'remove' => 'Уклони', 'add' => 'Додај', 'configure' => 'Конфигуриши', @@ -81,7 +81,7 @@ 'breadcrumb' => 'Навигација', 'status' => 'Стање', 'status_active' => 'Активан', - 'status_inactive' => 'Неактивно', + 'status_inactive' => 'Неактиван', 'never' => 'Никад', 'none' => 'Ништа', @@ -89,7 +89,7 @@ 'homepage' => 'Почетна страна', 'header_menu_expand' => 'Проширите мени заглавља', 'profile_menu' => 'Мени профила', - 'view_profile' => 'Погледај Профил', + 'view_profile' => 'Погледај профил', 'edit_profile' => 'Измени профил', 'dark_mode' => 'Тамни режим', 'light_mode' => 'Светли режим', @@ -111,5 +111,5 @@ 'terms_of_service' => 'Услови коришћења', // OpenSearch - 'opensearch_description' => 'Search :appName', + 'opensearch_description' => 'Претражи :appName', ]; diff --git a/lang/sr/editor.php b/lang/sr/editor.php index 2756775a9f2..0f64e1272ef 100644 --- a/lang/sr/editor.php +++ b/lang/sr/editor.php @@ -48,8 +48,8 @@ 'superscript' => 'Надскрипт', 'subscript' => 'Субкрипт', 'text_color' => 'Боја текста', - 'highlight_color' => 'Highlight color', - 'custom_color' => 'Боја текста', + 'highlight_color' => 'Боја наглашавања', + 'custom_color' => 'Прилагођена боја', 'remove_color' => 'Уклоните боју', 'background_color' => 'Боја позадине', 'align_left' => 'Поравнај лево', @@ -61,7 +61,7 @@ 'list_task' => 'Листа задатака', 'indent_increase' => 'Повећај увлачење', 'indent_decrease' => 'Умањи увлачење', - 'table' => 'Tabela', + 'table' => 'Табела', 'insert_image' => 'Уметни слику', 'insert_image_title' => 'Убаци/уреди слику', 'insert_link' => 'Убаци/измени везу', @@ -149,11 +149,11 @@ 'url' => 'УРЛ', 'text_to_display' => 'Текст за приказ', 'title' => 'Наслов', - 'browse_links' => 'Browse links', + 'browse_links' => 'Потражи везе', 'open_link' => 'Отвори везу', 'open_link_in' => 'Отвори везу у...', 'open_link_current' => 'Тренутни прозор', - 'open_link_new' => 'Нови Прозор', + 'open_link_new' => 'Нови прозор', 'remove_link' => 'Уклони везу', 'insert_collapsible' => 'Уредите склопиви блок', 'collapsible_unwrap' => 'Одмотати', @@ -166,8 +166,8 @@ 'about' => 'О уређивачу', 'about_title' => 'О уређивачу WYSIWYG', 'editor_license' => 'Уредничка лиценца и ауторска права', - 'editor_lexical_license' => 'This editor is built as a fork of :lexicalLink which is distributed under the MIT license.', - 'editor_lexical_license_link' => 'Full license details can be found here.', + 'editor_lexical_license' => 'Уређивач је изграђен као копија :lexicalLink који се дистрибуира под MIT лиценцом.', + 'editor_lexical_license_link' => 'Комплетни детаљи лиценце се могу пронаћи овде.', 'editor_tiny_license' => 'Овај уређивач је направљен помоћу :tinyLink који је обезбеђен под МИТ лиценцом.', 'editor_tiny_license_link' => 'Детаље о ауторским правима и лиценци за ТиниМЦЕ можете пронаћи овде.', 'save_continue' => 'Сачувај страницу и настави', diff --git a/lang/sr/entities.php b/lang/sr/entities.php index cd4e732f412..1c872f546fc 100644 --- a/lang/sr/entities.php +++ b/lang/sr/entities.php @@ -24,7 +24,7 @@ 'meta_updated_name' => 'Ажурирано :timeLength од :user', 'meta_owned_name' => 'Власништво :user', 'meta_reference_count' => 'Референтна од :count item|Референтна од :count items', - 'entity_select' => 'Избор ентитета', + 'entity_select' => 'Избор ставке', 'entity_select_lack_permission' => 'Немате потребне дозволе да изаберете ову ставку', 'images' => 'Слике', 'my_recent_drafts' => 'Моји недавни нацрти', @@ -38,39 +38,39 @@ 'export_html' => 'Садржана веб датотека', 'export_pdf' => 'PDF датотека', 'export_text' => 'Датотеке чистог текста', - 'export_md' => 'Markdown File', - 'export_zip' => 'Portable ZIP', + 'export_md' => 'Markdown датотека', + 'export_zip' => 'Портабилан ZIP', 'default_template' => 'Подразумевани шаблон странице', 'default_template_explain' => 'Доделите шаблон странице који ће се користити као подразумевани садржај за све странице креиране у оквиру ове ставке. Имајте на уму да ће се ово користити само ако креатор странице има приступ за преглед изабране странице шаблона.', 'default_template_select' => 'Изаберите страницу са шаблоном', - 'import' => 'Import', - 'import_validate' => 'Validate Import', - 'import_desc' => 'Import books, chapters & pages using a portable zip export from the same, or a different, instance. Select a ZIP file to proceed. After the file has been uploaded and validated you\'ll be able to configure & confirm the import in the next view.', - 'import_zip_select' => 'Select ZIP file to upload', - 'import_zip_validation_errors' => 'Errors were detected while validating the provided ZIP file:', - 'import_pending' => 'Pending Imports', - 'import_pending_none' => 'No imports have been started.', + 'import' => 'Увоз', + 'import_validate' => 'Потврди увоз', + 'import_desc' => 'Увезите књиге, поглавља и стране користећи портабилан zip извоз из исте или друге инстанце. Изаберите ZIP датотеку за наставак. Након што је датотека постављена и потврђена од ваше стране, моћи ћете да подесите и потврдите увоз у следећем приказу.', + 'import_zip_select' => 'Изаберите ZIP датотеку за постављање', + 'import_zip_validation_errors' => 'Откривене су грешке током провере достављене ZIP датотеке:', + 'import_pending' => 'Увози на чекању', + 'import_pending_none' => 'Ниједан увоз није започет.', 'import_continue' => 'Настави увоз', - 'import_continue_desc' => 'Review the content due to be imported from the uploaded ZIP file. When ready, run the import to add its contents to this system. The uploaded ZIP import file will be automatically removed on successful import.', - 'import_details' => 'Import Details', - 'import_run' => 'Run Import', - 'import_size' => ':size Import ZIP Size', - 'import_uploaded_at' => 'Uploaded :relativeTime', - 'import_uploaded_by' => 'Uploaded by', - 'import_location' => 'Import Location', - 'import_location_desc' => 'Select a target location for your imported content. You\'ll need the relevant permissions to create within the location you choose.', - 'import_delete_confirm' => 'Are you sure you want to delete this import?', - 'import_delete_desc' => 'This will delete the uploaded import ZIP file, and cannot be undone.', - 'import_errors' => 'Import Errors', - 'import_errors_desc' => 'The follow errors occurred during the import attempt:', - 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', - 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter', - 'breadcrumb_siblings_for_book' => 'Navigate siblings for book', - 'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf', + 'import_continue_desc' => 'Проверите садржај који ће бити увезен из отпремљене ZIP датотеке. Када сте спремни, покрените увоз да би сте додали садржај у систем. Отпремљена ZIP датотека ће аутоматски бити уклоњена по успешном увозу.', + 'import_details' => 'Детаљи увоза', + 'import_run' => 'Покрени увоз', + 'import_size' => ':size величина увозног ZIP-а', + 'import_uploaded_at' => 'Отпремљено :relativeTime', + 'import_uploaded_by' => 'Отпремио', + 'import_location' => 'Локација увоза', + 'import_location_desc' => 'Изаберите одредишну локацију за ваш увезени садржај. Биће вам потребне одговарајуће дозволе за прављење на локацији коју изаберете.', + 'import_delete_confirm' => 'Да ли заиста желите да обришете овај увоз?', + 'import_delete_desc' => 'Ово ће обрисати отпремљену ZIP датотеку, и није могућ опозив.', + 'import_errors' => 'Грешке увоза', + 'import_errors_desc' => 'Следеће грешке су се појавиле током покушаја увоза:', + 'breadcrumb_siblings_for_page' => 'Истражи сроднике стране', + 'breadcrumb_siblings_for_chapter' => 'Истражи сроднике поглавља', + 'breadcrumb_siblings_for_book' => 'Истражи сроднике књиге', + 'breadcrumb_siblings_for_bookshelf' => 'Истражи сроднике полице', // Permissions and restrictions 'permissions' => 'Дозволе', - 'permissions_desc' => 'Подесите дозволе овде да бисте заменили подразумеване дозволе које дају корисничке улоге.', + 'permissions_desc' => 'Овде подесите дозволе да бисте заменили подразумеване дозволе које дају корисничке улоге.', 'permissions_book_cascade' => 'Дозволе постављене за књиге ће се аутоматски пребацивати на подређена поглавља и странице, осим ако немају дефинисане сопствене дозволе.', 'permissions_chapter_cascade' => 'Дозволе постављене на поглављима ће се аутоматски каскадно пребацивати на подређене странице, осим ако немају дефинисане сопствене дозволе.', 'permissions_save' => 'Сачувај дозволе', @@ -84,397 +84,397 @@ 'search_results' => 'Резултати претраге', 'search_total_results_found' => ':count пронађених резултата|:count укупно пронађених резултата', 'search_clear' => 'Обриши претрагу', - 'search_no_pages' => 'No pages matched this search', - 'search_for_term' => 'Search for :term', - 'search_more' => 'More Results', - 'search_advanced' => 'Advanced Search', - 'search_terms' => 'Search Terms', - 'search_content_type' => 'Content Type', - 'search_exact_matches' => 'Exact Matches', - 'search_tags' => 'Tag Searches', - 'search_options' => 'Options', - 'search_viewed_by_me' => 'Viewed by me', - 'search_not_viewed_by_me' => 'Not viewed by me', - 'search_permissions_set' => 'Permissions set', - 'search_created_by_me' => 'Created by me', - 'search_updated_by_me' => 'Updated by me', - 'search_owned_by_me' => 'Owned by me', - 'search_date_options' => 'Date Options', - 'search_updated_before' => 'Updated before', - 'search_updated_after' => 'Updated after', - 'search_created_before' => 'Created before', - 'search_created_after' => 'Created after', - 'search_set_date' => 'Set Date', - 'search_update' => 'Update Search', + 'search_no_pages' => 'Ниједна страна се не поклапа са претрагом', + 'search_for_term' => 'Претражи :term', + 'search_more' => 'Више резултата', + 'search_advanced' => 'Напредна претрага', + 'search_terms' => 'Термини претраге', + 'search_content_type' => 'Тип садржаја', + 'search_exact_matches' => 'Тачно поклапање', + 'search_tags' => 'Претрага ознака', + 'search_options' => 'Опције', + 'search_viewed_by_me' => 'Гледао сам', + 'search_not_viewed_by_me' => 'Нисам гледао', + 'search_permissions_set' => 'Скуп дозвола', + 'search_created_by_me' => 'Направио сам', + 'search_updated_by_me' => 'Ажурирао сам', + 'search_owned_by_me' => 'Ја сам власник', + 'search_date_options' => 'Опције датума', + 'search_updated_before' => 'Ажурирано пре', + 'search_updated_after' => 'Ажурирано после', + 'search_created_before' => 'Направљено пре', + 'search_created_after' => 'Направљено после', + 'search_set_date' => 'Подеси датум', + 'search_update' => 'Ажурирај претрагу', // Shelves - 'shelf' => 'Shelf', + 'shelf' => 'Полица', 'shelves' => 'Полице', - 'x_shelves' => ':count Shelf|:count Shelves', - 'shelves_empty' => 'No shelves have been created', - 'shelves_create' => 'Create New Shelf', - 'shelves_popular' => 'Popular Shelves', - 'shelves_new' => 'New Shelves', - 'shelves_new_action' => 'New Shelf', - 'shelves_popular_empty' => 'The most popular shelves will appear here.', - 'shelves_new_empty' => 'The most recently created shelves will appear here.', - 'shelves_save' => 'Save Shelf', - 'shelves_books' => 'Books on this shelf', - 'shelves_add_books' => 'Add books to this shelf', - 'shelves_drag_books' => 'Drag books below to add them to this shelf', - 'shelves_empty_contents' => 'This shelf has no books assigned to it', - 'shelves_edit_and_assign' => 'Edit shelf to assign books', - 'shelves_edit_named' => 'Edit Shelf :name', - 'shelves_edit' => 'Edit Shelf', - 'shelves_delete' => 'Delete Shelf', - 'shelves_delete_named' => 'Delete Shelf :name', - 'shelves_delete_explain' => "This will delete the shelf with the name ':name'. Contained books will not be deleted.", - 'shelves_delete_confirmation' => 'Are you sure you want to delete this shelf?', - 'shelves_permissions' => 'Shelf Permissions', - 'shelves_permissions_updated' => 'Shelf Permissions Updated', - 'shelves_permissions_active' => 'Shelf Permissions Active', - 'shelves_permissions_cascade_warning' => 'Permissions on shelves do not automatically cascade to contained books. This is because a book can exist on multiple shelves. Permissions can however be copied down to child books using the option found below.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', - 'shelves_copy_permissions_to_books' => 'Copy Permissions to Books', - 'shelves_copy_permissions' => 'Copy Permissions', - 'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this shelf to all books contained within. Before activating, ensure any changes to the permissions of this shelf have been saved.', - 'shelves_copy_permission_success' => 'Shelf permissions copied to :count books', + 'x_shelves' => ':count полица|:count полице', + 'shelves_empty' => 'Није направљена ниједна полица', + 'shelves_create' => 'Направи нову полицу', + 'shelves_popular' => 'Популарне полице', + 'shelves_new' => 'Нове полице', + 'shelves_new_action' => 'Нова полица', + 'shelves_popular_empty' => 'Најпопуларније полице ће се појавити овде.', + 'shelves_new_empty' => 'Најскорије направљене полице ће се појавити овде.', + 'shelves_save' => 'Сачувај полицу', + 'shelves_books' => 'Књиге на овој полици', + 'shelves_add_books' => 'Додајте књиге на ову полицу', + 'shelves_drag_books' => 'Превуците књиге испод да их додате на ову полицу', + 'shelves_empty_contents' => 'Ова полица нема додељених књига', + 'shelves_edit_and_assign' => 'Уредите полицу да би сте доделили књиге', + 'shelves_edit_named' => 'Измени полицу :name', + 'shelves_edit' => 'Измени полицу', + 'shelves_delete' => 'Обриши полицу', + 'shelves_delete_named' => 'Обриши полицу :name', + 'shelves_delete_explain' => "Ово ће обрисати полицу са називом ':name'. Садржане књиге неће бити обрисане.", + 'shelves_delete_confirmation' => 'Да ли заиста желите да обришете ову полицу?', + 'shelves_permissions' => 'Дозволе полице', + 'shelves_permissions_updated' => 'Дозволе полице су ажуриране', + 'shelves_permissions_active' => 'Дозволе полице су активне', + 'shelves_permissions_cascade_warning' => 'Дозволе на полицама се не преносе аутоматски на садржане књиге. Ово је зато што књига може да постоји на вишеструко полица. Дозволе се међутим могу умножити на књиге наследнице користећи опцију испод.', + 'shelves_permissions_create' => 'Дозволе за прављење полица се користе само за дозволе умножавања на књиге наследнице користећи радњу испод. Оне не контролишу могућност прављења књига.', + 'shelves_copy_permissions_to_books' => 'Умножи дозволе на књиге', + 'shelves_copy_permissions' => 'Умножи дозволе', + 'shelves_copy_permissions_explain' => 'Ово ће применити тренутне поставке дозвола ове полице на све књиге садржане на њој. Пре активирања, потврдите да су сачуване све измене над дозволама ове полице.', + 'shelves_copy_permission_success' => 'Дозволе полице су умножене на :count књиге', // Books - 'book' => 'Book', - 'books' => 'Books', - 'x_books' => ':count Book|:count Books', - 'books_empty' => 'No books have been created', - 'books_popular' => 'Popular Books', - 'books_recent' => 'Recent Books', - 'books_new' => 'New Books', - 'books_new_action' => 'New Book', - 'books_popular_empty' => 'The most popular books will appear here.', - 'books_new_empty' => 'The most recently created books will appear here.', - 'books_create' => 'Create New Book', - 'books_delete' => 'Delete Book', - 'books_delete_named' => 'Delete Book :bookName', - 'books_delete_explain' => 'This will delete the book with the name \':bookName\'. All pages and chapters will be removed.', - 'books_delete_confirmation' => 'Are you sure you want to delete this book?', - 'books_edit' => 'Edit Book', - 'books_edit_named' => 'Edit Book :bookName', - 'books_form_book_name' => 'Book Name', - 'books_save' => 'Save Book', - 'books_permissions' => 'Book Permissions', - 'books_permissions_updated' => 'Book Permissions Updated', - 'books_empty_contents' => 'No pages or chapters have been created for this book.', - 'books_empty_create_page' => 'Create a new page', - 'books_empty_sort_current_book' => 'Sort the current book', - 'books_empty_add_chapter' => 'Add a chapter', - 'books_permissions_active' => 'Book Permissions Active', - 'books_search_this' => 'Search this book', - 'books_navigation' => 'Book Navigation', - 'books_sort' => 'Sort Book Contents', - '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', - 'books_sort_updated' => 'Sort by Updated Date', - 'books_sort_chapters_first' => 'Chapters First', - 'books_sort_chapters_last' => 'Chapters Last', - 'books_sort_show_other' => 'Show Other Books', - 'books_sort_save' => 'Save New Order', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', - 'books_copy' => 'Copy Book', - 'books_copy_success' => 'Book successfully copied', + 'book' => 'Књига', + 'books' => 'Књиге', + 'x_books' => ':count књига|:count књиге', + 'books_empty' => 'Није направљена ниједна књига', + 'books_popular' => 'Популарне књиге', + 'books_recent' => 'Недавне књиге', + 'books_new' => 'Нове књиге', + 'books_new_action' => 'Нова књига', + 'books_popular_empty' => 'Најпопуларније књиге ће се појавити овде.', + 'books_new_empty' => 'Најскорије направљене књиге ће се појавити овде.', + 'books_create' => 'Направи нову књигу', + 'books_delete' => 'Обриши књигу', + 'books_delete_named' => 'Обриши књигу :bookName', + 'books_delete_explain' => 'Ово ће обрисати књигу под називом \':bookName\'. Све стране и поглавља ће такође бити уклоњени.', + 'books_delete_confirmation' => 'Да ли заиста желите да обришете ову књигу?', + 'books_edit' => 'Измени књигу', + 'books_edit_named' => 'Измени књигу :bookName', + 'books_form_book_name' => 'Назив књиге', + 'books_save' => 'Сачувај књигу', + 'books_permissions' => 'Дозволе књиге', + 'books_permissions_updated' => 'Ажуриране су дозволе књиге', + 'books_empty_contents' => 'Нису направљене стране нити поглавља за ову књигу.', + 'books_empty_create_page' => 'Направи нову страну', + 'books_empty_sort_current_book' => 'Разврстај тренутну књигу', + 'books_empty_add_chapter' => 'Додај поглавље', + 'books_permissions_active' => 'Дозволе књиге су активне', + 'books_search_this' => 'Претражи ову књигу', + 'books_navigation' => 'Навигација књиге', + 'books_sort' => 'Разврстај садржај књиге', + 'books_sort_desc' => 'Преместите поглавља и стране унутар књиге да би сте реорганизовали њен садржај. Друге књиге се могу додати што омогућава лако премештање поглавља и страна међу књигама. Опционо се може подесити правило за аутоматско разврставање садржаја ове књиге након измена.', + 'books_sort_auto_sort' => 'Опције аутоматског разврставања', + 'books_sort_auto_sort_active' => 'Активно је аутоматско разврставање: :sortName', + 'books_sort_auto_sort_creation_hint' => 'Правила опција аутоматског разврставања могу бити направљена под поставкама "Спискови и разврставање" од стране корисника са одговарајућим дозволама.', + 'books_sort_named' => 'Разврстај књигу :bookName', + 'books_sort_name' => 'Разврстај по називу', + 'books_sort_created' => 'Разврстај по датуму прављења', + 'books_sort_updated' => 'Разврстај по датуму ажурирања', + 'books_sort_chapters_first' => 'Прво поглавља', + 'books_sort_chapters_last' => 'Последње поглавља', + 'books_sort_show_other' => 'Прикажи друге књиге', + 'books_sort_save' => 'Сачувај нови редослед', + 'books_sort_show_other_desc' => 'Овде додајте друге књиге да би сте их уврстили у операцију разврставања и омогућили лаку реорганизацију међу књигама.', + 'books_sort_move_up' => 'Помери горе', + 'books_sort_move_down' => 'Помери доле', + 'books_sort_move_prev_book' => 'Помери до претходне књиге', + 'books_sort_move_next_book' => 'Помери до следеће књиге', + 'books_sort_move_prev_chapter' => 'Помери у претходно поглавље', + 'books_sort_move_next_chapter' => 'Помери у следеће поглавље', + 'books_sort_move_book_start' => 'Помери на почетак књиге', + 'books_sort_move_book_end' => 'Помери на крај књиге', + 'books_sort_move_before_chapter' => 'Помери пре поглавља', + 'books_sort_move_after_chapter' => 'Помери после поглавља', + 'books_copy' => 'Умножи књигу', + 'books_copy_success' => 'Књига је успешно умножена', // Chapters - 'chapter' => 'Chapter', - 'chapters' => 'Chapters', - 'x_chapters' => ':count Chapter|:count Chapters', - 'chapters_popular' => 'Popular Chapters', - 'chapters_new' => 'New Chapter', - 'chapters_create' => 'Create New Chapter', - 'chapters_delete' => 'Delete Chapter', - 'chapters_delete_named' => 'Delete Chapter :chapterName', - 'chapters_delete_explain' => 'This will delete the chapter with the name \':chapterName\'. All pages that exist within this chapter will also be deleted.', - 'chapters_delete_confirm' => 'Are you sure you want to delete this chapter?', - 'chapters_edit' => 'Edit Chapter', - 'chapters_edit_named' => 'Edit Chapter :chapterName', - 'chapters_save' => 'Save Chapter', - 'chapters_move' => 'Move Chapter', - 'chapters_move_named' => 'Move Chapter :chapterName', - 'chapters_copy' => 'Copy Chapter', - 'chapters_copy_success' => 'Chapter successfully copied', - 'chapters_permissions' => 'Chapter Permissions', - 'chapters_empty' => 'No pages are currently in this chapter.', - 'chapters_permissions_active' => 'Chapter Permissions Active', - 'chapters_permissions_success' => 'Chapter Permissions Updated', - 'chapters_search_this' => 'Search this chapter', - 'chapter_sort_book' => 'Sort Book', + 'chapter' => 'Поглавље', + 'chapters' => 'Поглавља', + 'x_chapters' => ':count поглавље|:count поглавља', + 'chapters_popular' => 'Популарна поглавља', + 'chapters_new' => 'Ново поглавље', + 'chapters_create' => 'Направи ново поглавље', + 'chapters_delete' => 'Обриши поглавље', + 'chapters_delete_named' => 'Обриши поглавље :chapterName', + 'chapters_delete_explain' => 'Ово ће обрисати поглавље са називом \':chapterName\'. Све стране које постоје унутар овог поглавља ће такође бити обрисане.', + 'chapters_delete_confirm' => 'Да ли заиста желите да обришете ово поглавље?', + 'chapters_edit' => 'Измени поглавље', + 'chapters_edit_named' => 'Измени поглавље :chapterName', + 'chapters_save' => 'Сачувај поглавље', + 'chapters_move' => 'Премести поглавље', + 'chapters_move_named' => 'Премести поглавље :chapterName', + 'chapters_copy' => 'Умножи поглавље', + 'chapters_copy_success' => 'Поглавље успешно умножено', + 'chapters_permissions' => 'Дозволе поглавља', + 'chapters_empty' => 'Тренутно нема страница у овом поглављу.', + 'chapters_permissions_active' => 'Активне су дозволе поглавља', + 'chapters_permissions_success' => 'Дозволе поглавља су ажуриране', + 'chapters_search_this' => 'Претражи ово поглавље', + 'chapter_sort_book' => 'Разврстај књигу', // Pages - 'page' => 'Page', - 'pages' => 'Pages', - 'x_pages' => ':count Page|:count Pages', - 'pages_popular' => 'Popular Pages', - 'pages_new' => 'New Page', - 'pages_attachments' => 'Attachments', - 'pages_navigation' => 'Page Navigation', - 'pages_delete' => 'Delete Page', - 'pages_delete_named' => 'Delete Page :pageName', - 'pages_delete_draft_named' => 'Delete Draft Page :pageName', - 'pages_delete_draft' => 'Delete Draft Page', - 'pages_delete_success' => 'Page deleted', - 'pages_delete_draft_success' => 'Draft page deleted', - 'pages_delete_warning_template' => 'This page is in active use as a book or chapter default page template. These books or chapters will no longer have a default page template assigned after this page is deleted.', - 'pages_delete_confirm' => 'Are you sure you want to delete this page?', - 'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?', - 'pages_editing_named' => 'Editing Page :pageName', - 'pages_edit_draft_options' => 'Draft Options', - 'pages_edit_save_draft' => 'Save Draft', - 'pages_edit_draft' => 'Edit Page Draft', - 'pages_editing_draft' => 'Editing Draft', - 'pages_editing_page' => 'Editing Page', - 'pages_edit_draft_save_at' => 'Draft saved at ', - 'pages_edit_delete_draft' => 'Delete Draft', - 'pages_edit_delete_draft_confirm' => 'Are you sure you want to delete your draft page changes? All of your changes, since the last full save, will be lost and the editor will be updated with the latest page non-draft save state.', - 'pages_edit_discard_draft' => 'Discard Draft', - 'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor', - 'pages_edit_switch_to_markdown_clean' => '(Clean Content)', - 'pages_edit_switch_to_markdown_stable' => '(Stable Content)', - 'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor', - 'pages_edit_switch_to_new_wysiwyg' => 'Switch to new WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', - 'pages_edit_set_changelog' => 'Set Changelog', - 'pages_edit_enter_changelog_desc' => 'Enter a brief description of the changes you\'ve made', - 'pages_edit_enter_changelog' => 'Enter Changelog', - 'pages_editor_switch_title' => 'Switch Editor', - 'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?', - 'pages_editor_switch_consider_following' => 'Consider the following when changing editors:', - 'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.', - 'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.', - 'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.', - 'pages_save' => 'Save Page', - 'pages_title' => 'Page Title', - 'pages_name' => 'Page Name', - 'pages_md_editor' => 'Editor', - 'pages_md_preview' => 'Preview', - 'pages_md_insert_image' => 'Insert Image', - 'pages_md_insert_link' => 'Insert Entity Link', - 'pages_md_insert_drawing' => 'Insert Drawing', - 'pages_md_show_preview' => 'Show preview', - 'pages_md_sync_scroll' => 'Sync preview scroll', - 'pages_md_plain_editor' => 'Plaintext editor', - 'pages_drawing_unsaved' => 'Unsaved Drawing Found', - 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', - 'pages_not_in_chapter' => 'Page is not in a chapter', - 'pages_move' => 'Move Page', - 'pages_copy' => 'Copy Page', - 'pages_copy_desination' => 'Copy Destination', - 'pages_copy_success' => 'Page successfully copied', - 'pages_permissions' => 'Page Permissions', - 'pages_permissions_success' => 'Page permissions updated', - 'pages_revision' => 'Revision', - 'pages_revisions' => 'Page Revisions', - 'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.', - 'pages_revisions_named' => 'Page Revisions for :pageName', - 'pages_revision_named' => 'Page Revision for :pageName', - 'pages_revision_restored_from' => 'Restored from #:id; :summary', - 'pages_revisions_created_by' => 'Created By', - 'pages_revisions_date' => 'Revision Date', + 'page' => 'Страна', + 'pages' => 'Стране', + 'x_pages' => ':count страна|:count стране', + 'pages_popular' => 'Популарне стране', + 'pages_new' => 'Нова страна', + 'pages_attachments' => 'Прилози', + 'pages_navigation' => 'Навигација стране', + 'pages_delete' => 'Обриши страну', + 'pages_delete_named' => 'Обриши страну :pageName', + 'pages_delete_draft_named' => 'Обриши нацрт стране :pageName', + 'pages_delete_draft' => 'Обриши нацрт стране', + 'pages_delete_success' => 'Страна је обрисана', + 'pages_delete_draft_success' => 'Нацрт стране је обрисан', + 'pages_delete_warning_template' => 'Ова страна је у активној употреби као подразумевани шаблон књиге или поглавља. Ове књиге или поглавља више неће имати додељен подразумевани шаблон стране након обришете ову страну.', + 'pages_delete_confirm' => 'Да ли заиста желите да обришете ову страну?', + 'pages_delete_draft_confirm' => 'Да ли заиста желите да обришете овај нацрт?', + 'pages_editing_named' => 'Уређивање стране :pageName', + 'pages_edit_draft_options' => 'Опције нацрта', + 'pages_edit_save_draft' => 'Сачувај нацрт', + 'pages_edit_draft' => 'Измени нацрт стране', + 'pages_editing_draft' => 'Уређивање нацрта', + 'pages_editing_page' => 'Уређивање стране', + 'pages_edit_draft_save_at' => 'Нацрт сачуван у ', + 'pages_edit_delete_draft' => 'Обриши нацрт', + 'pages_edit_delete_draft_confirm' => 'Да ли заиста желите да обришете ваш нацрт стране? Све ваше измене, од последњег пуног снимања ће бити изгубљене и уређивач ће бити ажуриран на последње сачувано стање стране без нацрта.', + 'pages_edit_discard_draft' => 'Одбаци нацрт', + 'pages_edit_switch_to_markdown' => 'Пребаци се на Маркдаун уређивач', + 'pages_edit_switch_to_markdown_clean' => '(чист садржај)', + 'pages_edit_switch_to_markdown_stable' => '(стабилан садржај)', + 'pages_edit_switch_to_wysiwyg' => 'Пребаци на WYSIWYG уређивач', + 'pages_edit_switch_to_new_wysiwyg' => 'Пребаци на нови WYSIWYG', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(у бета тестирању)', + 'pages_edit_set_changelog' => 'Напомена о изменама', + 'pages_edit_enter_changelog_desc' => 'Унесите кратак опис измена које сте извршили', + 'pages_edit_enter_changelog' => 'Упишите запис о променама', + 'pages_editor_switch_title' => 'Промени уређивач', + 'pages_editor_switch_are_you_sure' => 'Да ли заиста желите да промените уређивач за ову страну?', + 'pages_editor_switch_consider_following' => 'Размотрите следеће када мењате уређиваче:', + 'pages_editor_switch_consideration_a' => 'Једном сачуване, опције новог уређивача ће се користити за све будуће уреднике, укључујући оне који немају могућност да сами мењају тип уређивача.', + 'pages_editor_switch_consideration_b' => 'Ово потенцијално може довести до губитка детаља и синтаксе у одређеним случајевима.', + 'pages_editor_switch_consideration_c' => 'Ознаке или записи промена, начињени након последњег снимања, неће се задржати преко ове измене.', + 'pages_save' => 'Сачувај страну', + 'pages_title' => 'Наслов стране', + 'pages_name' => 'Назив стране', + 'pages_md_editor' => 'Уређивач', + 'pages_md_preview' => 'Преглед', + 'pages_md_insert_image' => 'Уметни слику', + 'pages_md_insert_link' => 'Уметни везу до ентитета', + 'pages_md_insert_drawing' => 'Уметни цртеж', + 'pages_md_show_preview' => 'Прикажи преглед', + 'pages_md_sync_scroll' => 'Синхронизуј положај прегледа', + 'pages_md_plain_editor' => 'Уређивач чистог текста', + 'pages_drawing_unsaved' => 'Пронађен је несачуван цртеж', + 'pages_drawing_unsaved_confirm' => 'Пронађени су подаци о несачуваном цртежу од претходног неуспелог покушаја снимања. Да ли желите да га повратите и наставите са уређивањем овог несачуваног цртежа?', + 'pages_not_in_chapter' => 'Страна није у поглављу', + 'pages_move' => 'Премести страну', + 'pages_copy' => 'Умножи страну', + 'pages_copy_desination' => 'Умножи одредиште', + 'pages_copy_success' => 'Страна је успешно умножена', + 'pages_permissions' => 'Дозволе стране', + 'pages_permissions_success' => 'Ажуриране су дозволе стране', + 'pages_revision' => 'Ревизија', + 'pages_revisions' => 'Ревизије стране', + 'pages_revisions_desc' => 'спод су наведене све ревизије ове стране. Можете их погледати, упоредити и вратити старе верзије стране ако имате дозвола за то. Пуна историја стране се можда не може сагледати овде с обзиром да, у зависности од подешавања система, старе ревизије су можда аутоматски обрисане.', + 'pages_revisions_named' => 'Ревизије стране за :pageName', + 'pages_revision_named' => 'Ревизија стране за :pageName', + 'pages_revision_restored_from' => 'Враћено из #:id; :summary', + 'pages_revisions_created_by' => 'Направио', + 'pages_revisions_date' => 'Датум ревизије', 'pages_revisions_number' => '#', - 'pages_revisions_sort_number' => 'Revision Number', - 'pages_revisions_numbered' => 'Revision #:id', - 'pages_revisions_numbered_changes' => 'Revision #:id Changes', - 'pages_revisions_editor' => 'Editor Type', - 'pages_revisions_changelog' => 'Changelog', - 'pages_revisions_changes' => 'Changes', - 'pages_revisions_current' => 'Current Version', - 'pages_revisions_preview' => 'Preview', - 'pages_revisions_restore' => 'Restore', - 'pages_revisions_none' => 'This page has no revisions', - 'pages_copy_link' => 'Copy Link', - 'pages_edit_content_link' => 'Jump to section in editor', - 'pages_pointer_enter_mode' => 'Enter section select mode', - 'pages_pointer_label' => 'Page Section Options', - 'pages_pointer_permalink' => 'Page Section Permalink', - 'pages_pointer_include_tag' => 'Page Section Include Tag', - 'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag', - 'pages_pointer_toggle_include' => 'Include tag mode, Press to show permalink', - 'pages_permissions_active' => 'Page Permissions Active', - 'pages_initial_revision' => 'Initial publish', - 'pages_references_update_revision' => 'System auto-update of internal links', - 'pages_initial_name' => 'New Page', - 'pages_editing_draft_notification' => 'You are currently editing a draft that was last saved :timeDiff.', - 'pages_draft_edited_notification' => 'This page has been updated by since that time. It is recommended that you discard this draft.', - 'pages_draft_page_changed_since_creation' => 'This page has been updated since this draft was created. It is recommended that you discard this draft or take care not to overwrite any page changes.', + 'pages_revisions_sort_number' => 'Број ревизије', + 'pages_revisions_numbered' => 'Ревизија #:id', + 'pages_revisions_numbered_changes' => 'Промене ревизије #:id', + 'pages_revisions_editor' => 'Тип уређивача', + 'pages_revisions_changelog' => 'Запис промене', + 'pages_revisions_changes' => 'Промене', + 'pages_revisions_current' => 'Тренутна верзија', + 'pages_revisions_preview' => 'Преглед', + 'pages_revisions_restore' => 'Враћање', + 'pages_revisions_none' => 'Ова страна нема ревизија', + 'pages_copy_link' => 'Умножи везу', + 'pages_edit_content_link' => 'Скочи на секцију у уређивачу', + 'pages_pointer_enter_mode' => 'Уђите у режим избора секције', + 'pages_pointer_label' => 'Опције секције стране', + 'pages_pointer_permalink' => 'Стална веза секције стране', + 'pages_pointer_include_tag' => 'Секција стране садржи ознаку', + 'pages_pointer_toggle_link' => 'Режим сталне везе. Притисните за приказ садржане ознаке', + 'pages_pointer_toggle_include' => 'Режим садржане ознаке. Притисните за приказ сталне везе', + 'pages_permissions_active' => 'Активне су дозволе стране', + 'pages_initial_revision' => 'Прва објава', + 'pages_references_update_revision' => 'Системско аутоматско ажурирање интерних веза', + 'pages_initial_name' => 'Нова страна', + 'pages_editing_draft_notification' => 'Тренутно уређујете нацрт који је сачуван :timeDiff.', + 'pages_draft_edited_notification' => 'Ова страна је ажурирана од тада. Препоручује се да одбаците овај нацрт.', + 'pages_draft_page_changed_since_creation' => 'Ова страна је ажурирана након прављења овог нацрта. Препоручује се да обаците овај нацрт или да се потрудите да не препишете било какве измене на страни.', 'pages_draft_edit_active' => [ - 'start_a' => ':count users have started editing this page', - 'start_b' => ':userName has started editing this page', - 'time_a' => 'since the page was last updated', - 'time_b' => 'in the last :minCount minutes', - 'message' => ':start :time. Take care not to overwrite each other\'s updates!', + 'start_a' => ':count корисника је започело уређивање ове стране', + 'start_b' => ':userName је започео уређивање ове стране', + 'time_a' => 'од када је страна последи пут ажурирана', + 'time_b' => 'у последњих:minCount минута', + 'message' => ':start :time. Водите рачуна да једни другима не препишете измене!', ], - 'pages_draft_discarded' => 'Draft discarded! The editor has been updated with the current page content', - 'pages_draft_deleted' => 'Draft deleted! The editor has been updated with the current page content', - 'pages_specific' => 'Specific Page', - 'pages_is_template' => 'Page Template', + 'pages_draft_discarded' => 'Нацрт је одбачен! Уређивач је ажуриран са тренутним садржајем стране', + 'pages_draft_deleted' => 'Нацрт је обисан! Уређивач је ажуриран са тренутним садржајем стране', + 'pages_specific' => 'Одређена страна', + 'pages_is_template' => 'Шаблон стране', // 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', - 'shelf_tags' => 'Shelf Tags', - 'tag' => 'Tag', - 'tags' => 'Tags', - 'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.', - 'tag_name' => 'Tag Name', - 'tag_value' => 'Tag Value (Optional)', - 'tags_explain' => "Add some tags to better categorise your content. \n You can assign a value to a tag for more in-depth organisation.", - 'tags_add' => 'Add another tag', - 'tags_remove' => 'Remove this tag', - 'tags_usages' => 'Total tag usages', - 'tags_assigned_pages' => 'Assigned to Pages', - 'tags_assigned_chapters' => 'Assigned to Chapters', - 'tags_assigned_books' => 'Assigned to Books', - 'tags_assigned_shelves' => 'Assigned to Shelves', - 'tags_x_unique_values' => ':count unique values', - 'tags_all_values' => 'All values', - 'tags_view_tags' => 'View Tags', - 'tags_view_existing_tags' => 'View existing tags', - 'tags_list_empty_hint' => 'Tags can be assigned via the page editor sidebar or while editing the details of a book, chapter or shelf.', - 'attachments' => 'Attachments', - 'attachments_explain' => 'Upload some files or attach some links to display on your page. These are visible in the page sidebar.', - 'attachments_explain_instant_save' => 'Changes here are saved instantly.', - 'attachments_upload' => 'Upload File', - 'attachments_link' => 'Attach Link', - 'attachments_upload_drop' => 'Alternatively you can drag and drop a file here to upload it as an attachment.', - 'attachments_set_link' => 'Set Link', - 'attachments_delete' => 'Are you sure you want to delete this attachment?', - 'attachments_dropzone' => 'Drop files here to upload', - 'attachments_no_files' => 'No files have been uploaded', - 'attachments_explain_link' => 'You can attach a link if you\'d prefer not to upload a file. This can be a link to another page or a link to a file in the cloud.', - 'attachments_link_name' => 'Link Name', - 'attachment_link' => 'Attachment link', - 'attachments_link_url' => 'Link to file', - 'attachments_link_url_hint' => 'Url of site or file', - 'attach' => 'Attach', - 'attachments_insert_link' => 'Add Attachment Link to Page', - 'attachments_edit_file' => 'Edit File', - 'attachments_edit_file_name' => 'File Name', - 'attachments_edit_drop_upload' => 'Drop files or click here to upload and overwrite', - 'attachments_order_updated' => 'Attachment order updated', - 'attachments_updated_success' => 'Attachment details updated', - 'attachments_deleted' => 'Attachment deleted', - 'attachments_file_uploaded' => 'File successfully uploaded', - 'attachments_file_updated' => 'File successfully updated', - 'attachments_link_attached' => 'Link successfully attached to page', - 'templates' => 'Templates', - 'templates_set_as_template' => 'Page is a template', - 'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.', - 'templates_replace_content' => 'Replace page content', - 'templates_append_content' => 'Append to page content', - 'templates_prepend_content' => 'Prepend to page content', + 'toggle_sidebar' => 'Приказ помоћне траке', + 'page_contents' => 'Садржај стране', + 'page_contents_none' => 'Није пронађено ниједно заглавље у садржају стране.', + 'page_contents_info' => 'Мени садржаја се генерише према форматима заглавља коришћеним на овој страни.', + 'page_tags' => 'Ознаке стране', + 'chapter_tags' => 'Ознаке поглавља', + 'book_tags' => 'Ознаке књиге', + 'shelf_tags' => 'Ознаке полице', + 'tag' => 'Ознака', + 'tags' => 'Ознаке', + 'tags_index_desc' => 'Ознаке се могу придодати садржају унутар система како би се применио флексибилан облик категоризације. Ознаке могу имати кључ и вредност, док је вредност опциона. Када су примењене, садржај се може претраживати коришћењем назива ознаке и вредности.', + 'tag_name' => 'Назив ознаке', + 'tag_value' => 'Вредност ознаке (опционо)', + 'tags_explain' => "Додај неке ознаке за бољу категоризацију вашег садржаја. \n Можете доделити вредност ознаци за још прецизнију организацију.", + 'tags_add' => 'Додај још једну ознаку', + 'tags_remove' => 'Уклони ову ознаку', + 'tags_usages' => 'Укупна употреба ознака', + 'tags_assigned_pages' => 'Додељено странама', + 'tags_assigned_chapters' => 'Додељено поглављима', + 'tags_assigned_books' => 'Додељено књигама', + 'tags_assigned_shelves' => 'Додељено полицама', + 'tags_x_unique_values' => ':count јединствених вредности', + 'tags_all_values' => 'Све вредности', + 'tags_view_tags' => 'Преглед ознака', + 'tags_view_existing_tags' => 'Погледај постојеће ознаке', + 'tags_list_empty_hint' => 'Ознаке се могу доделити путем траке са стране у уређивачу стране док се уређују детаљи о књизи, поглавља или полице.', + 'attachments' => 'Прилози', + 'attachments_explain' => 'Отпремите неке датотеке или прикачите неке везе за приказ на вашој страни. Оне су видљиве на помоћној траци стране.', + 'attachments_explain_instant_save' => 'Измене овде су моментално сачуване.', + 'attachments_upload' => 'Постави датотеку', + 'attachments_link' => 'Закачи везу', + 'attachments_upload_drop' => 'Алтернативно можете превући и отпустити датотеку овде да би сте је отпремили као прилог.', + 'attachments_set_link' => 'Подеси везу', + 'attachments_delete' => 'Да ли заиста желите да обришете овај прилог?', + 'attachments_dropzone' => 'Отпустите датотеке овде да их отпремите', + 'attachments_no_files' => 'Ниједна датотека није постављена', + 'attachments_explain_link' => 'Можете закачити везу ако не желите да отпремате датотеку. Ово може бити веза ка другој страни или датотека у облаку.', + 'attachments_link_name' => 'Назив везе', + 'attachment_link' => 'Веза прилога', + 'attachments_link_url' => 'Веза до датотеке', + 'attachments_link_url_hint' => 'Адреса сајта или датотеке', + 'attach' => 'Закачи', + 'attachments_insert_link' => 'Додај везу прилога на страну', + 'attachments_edit_file' => 'Измени датотеку', + 'attachments_edit_file_name' => 'Назив датотеке', + 'attachments_edit_drop_upload' => 'Отпустите датотеке или кликните овде да отпремите и препишете', + 'attachments_order_updated' => 'Ажуриран је редослед прилога', + 'attachments_updated_success' => 'Ажурирани су детаљи прилога', + 'attachments_deleted' => 'Прилог је обрисан', + 'attachments_file_uploaded' => 'Датотека је успешно отпремљена', + 'attachments_file_updated' => 'Датотека је успешно ажурирана', + 'attachments_link_attached' => 'Веза је успешно закачена на страну', + 'templates' => 'Шаблони', + 'templates_set_as_template' => 'Страна је шаблон', + 'templates_explain_set_as_template' => 'Можете подесити ову страну као шаблон како би њен садржај био искоришћен при прављењу других страна. Други корисници ће моћи да користе овај шаблон ако имају дозволу да прегледају ову страну.', + 'templates_replace_content' => 'Замени садржај стране', + 'templates_append_content' => 'Придодај садржају стране', + 'templates_prepend_content' => 'Уметни пре садржаја стране', // Profile View - 'profile_user_for_x' => 'User for :time', - 'profile_created_content' => 'Created Content', - 'profile_not_created_pages' => ':userName has not created any pages', - 'profile_not_created_chapters' => ':userName has not created any chapters', - 'profile_not_created_books' => ':userName has not created any books', - 'profile_not_created_shelves' => ':userName has not created any shelves', + 'profile_user_for_x' => 'Корисник последњих :time', + 'profile_created_content' => 'Направљени садржај', + 'profile_not_created_pages' => ':userName није направио ниједну страницу', + 'profile_not_created_chapters' => ':userName није направио ниједно поглавље', + 'profile_not_created_books' => ':userName није направио ниједну књигу', + 'profile_not_created_shelves' => ':userName није направио ниједну полицу', // Comments - 'comment' => 'Comment', - 'comments' => 'Comments', - 'comment_add' => 'Add Comment', - 'comment_none' => 'No comments to display', - 'comment_placeholder' => 'Leave a comment here', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', - 'comment_save' => 'Save Comment', - 'comment_new' => 'New Comment', - 'comment_created' => 'commented :createDiff', - 'comment_updated' => 'Updated :updateDiff by :username', - 'comment_updated_indicator' => 'Updated', - 'comment_deleted_success' => 'Comment deleted', - 'comment_created_success' => 'Comment added', - 'comment_updated_success' => 'Comment updated', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', - 'comment_delete_confirm' => 'Are you sure you want to delete this comment?', - 'comment_in_reply_to' => 'In reply to :commentId', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', - 'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.', + 'comment' => 'Коментар', + 'comments' => 'Коментари', + 'comment_add' => 'Додај коментар', + 'comment_none' => 'Нема коментара за приказ', + 'comment_placeholder' => 'Оставите коментар овде', + 'comment_thread_count' => ':count коментар у разговору|:count коментара у разговору', + 'comment_archived_count' => ':count архивирано', + 'comment_archived_threads' => 'Архивиране приче', + 'comment_save' => 'Сачувај коментар', + 'comment_new' => 'Нови коментар', + 'comment_created' => 'коментарисао :createDiff', + 'comment_updated' => 'Измењено :updateDiff од стране :username', + 'comment_updated_indicator' => 'Ажурирано', + 'comment_deleted_success' => 'Коментар обрисан', + 'comment_created_success' => 'Коментар додат', + 'comment_updated_success' => 'Коментар измењен', + 'comment_archive_success' => 'Коментар архиивиран', + 'comment_unarchive_success' => 'Коментар деархивиран', + 'comment_view' => 'Погледај коментар', + 'comment_jump_to_thread' => 'Пређи на разговор', + 'comment_delete_confirm' => 'Да ли заиста желите да обришете овај коментар?', + 'comment_in_reply_to' => 'Као одговор на :commentId', + 'comment_reference' => 'Референца', + 'comment_reference_outdated' => '(застарело)', + 'comment_editor_explain' => 'Ево коментара који су остављени на овој страни. Коментари се могу додавати и може им се управљати при прегледу сачуване стране.', // Revision - 'revision_delete_confirm' => 'Are you sure you want to delete this revision?', - 'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.', - 'revision_cannot_delete_latest' => 'Cannot delete the latest revision.', + 'revision_delete_confirm' => 'Да ли заиста желите да обришете ову ревизију?', + 'revision_restore_confirm' => 'Да ли заиста желите да вратите ову ревизију? Садржај тренутне стране ће бити замењен.', + 'revision_cannot_delete_latest' => 'Није могуће обрисати последњу ревизију.', // Copy view - 'copy_consider' => 'Please consider the below when copying content.', - 'copy_consider_permissions' => 'Custom permission settings will not be copied.', - 'copy_consider_owner' => 'You will become the owner of all copied content.', - 'copy_consider_images' => 'Page image files will not be duplicated & the original images will retain their relation to the page they were originally uploaded to.', - 'copy_consider_attachments' => 'Page attachments will not be copied.', - 'copy_consider_access' => 'A change of location, owner or permissions may result in this content being accessible to those previously without access.', + 'copy_consider' => 'Узмите у обзир следеће када умножавате садржај.', + 'copy_consider_permissions' => 'Поставке прилагођених дозвола неће бити умножене.', + 'copy_consider_owner' => 'Постаћете власник свег умноженог садржаја.', + 'copy_consider_images' => 'Датотеке слика стране неће бити умножене и оригиналне слике ће задржати однос према страни на коју су оригинално отпремљене.', + 'copy_consider_attachments' => 'Прилози стране неће бити умножени.', + 'copy_consider_access' => 'Промена локације, власника или дозвола може проузроковати да садржај постане доступан онима који раније нису имали приступ.', // Conversions - 'convert_to_shelf' => 'Convert to Shelf', - 'convert_to_shelf_contents_desc' => 'You can convert this book to a new shelf with the same contents. Chapters contained within this book will be converted to new books. If this book contains any pages, that are not in a chapter, this book will be renamed and contain such pages, and this book will become part of the new shelf.', - 'convert_to_shelf_permissions_desc' => 'Any permissions set on this book will be copied to the new shelf and to all new child books that don\'t have their own permissions enforced. Note that permissions on shelves do not auto-cascade to content within, as they do for books.', - 'convert_book' => 'Convert Book', - 'convert_book_confirm' => 'Are you sure you want to convert this book?', - 'convert_undo_warning' => 'This cannot be as easily undone.', - 'convert_to_book' => 'Convert to Book', - 'convert_to_book_desc' => 'You can convert this chapter to a new book with the same contents. Any permissions set on this chapter will be copied to the new book but any inherited permissions, from the parent book, will not be copied which could lead to a change of access control.', - 'convert_chapter' => 'Convert Chapter', - 'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?', + 'convert_to_shelf' => 'Претвори у полицу', + 'convert_to_shelf_contents_desc' => 'Можете претворити ову књигу у нову полицу са истим садржајем. Поглавља садржана унутар ове књиге ће бити претворена у нове књиге. Ако ова књига садржи икакве стране, које нису у поглављу, ова књига ће бити преименована и садржаће те стане и та књига ће постати део нове полице.', + 'convert_to_shelf_permissions_desc' => 'Све дозволе подешене над овом књигом ће бити умножене на нову полицу и на све нове књиге наследнике које немају приморане сопствене дозволе. Напомена да се дозволе на полицама на преносе на њихов садржај, као што је то случај са књигама.', + 'convert_book' => 'Претвори књигу', + 'convert_book_confirm' => 'Да ли заиста желите да претворите ову књигу?', + 'convert_undo_warning' => 'Ово не може лако бити повраћено.', + 'convert_to_book' => 'Претвори у књигу', + 'convert_to_book_desc' => 'Можете да претворите ово поглавље у нову књигу са истим садржајем. Све дозволе подешене над овим поглављем ће бити умножене на нову књигу али све наслеђене дозволе, од књиге родитеља, неће бити умножене што ће довести д промене у контрол приступа.', + 'convert_chapter' => 'Претвори поглавље', + 'convert_chapter_confirm' => 'Да ли заиста желите да претворите ово поглавље?', // References - 'references' => 'References', - 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', + 'references' => 'Референце', + 'references_none' => 'Нема праћених референци ка овој ставки.', + 'references_to_desc' => 'Испод је наведен сав познати садржај у систему који је повезан са овом ставком.', // Watch Options - 'watch' => 'Watch', - 'watch_title_default' => 'Default Preferences', - 'watch_desc_default' => 'Revert watching to just your default notification preferences.', - 'watch_title_ignore' => 'Ignore', - 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', - 'watch_title_new' => 'New Pages', - 'watch_desc_new' => 'Notify when any new page is created within this item.', - 'watch_title_updates' => 'All Page Updates', - 'watch_desc_updates' => 'Notify upon all new pages and page changes.', - 'watch_desc_updates_page' => 'Notify upon all page changes.', - 'watch_title_comments' => 'All Page Updates & Comments', - 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', - 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', - 'watch_change_default' => 'Change default notification preferences', - 'watch_detail_ignore' => 'Ignoring notifications', - 'watch_detail_new' => 'Watching for new pages', - 'watch_detail_updates' => 'Watching new pages and updates', - 'watch_detail_comments' => 'Watching new pages, updates & comments', - 'watch_detail_parent_book' => 'Watching via parent book', - 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', - 'watch_detail_parent_chapter' => 'Watching via parent chapter', - 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', + 'watch' => 'Прати', + 'watch_title_default' => 'Подразумевана подешавања', + 'watch_desc_default' => 'Вратите праћење на ваша подразумевана подешавања обавештавања.', + 'watch_title_ignore' => 'Игнориши', + 'watch_desc_ignore' => 'Игнориши сва обавештења, укључујући она из подешавања на корисничком нивоу.', + 'watch_title_new' => 'Нове стране', + 'watch_desc_new' => 'Обавести када се направи икаква нова страна унутар ове ставке.', + 'watch_title_updates' => 'Све измене странице', + 'watch_desc_updates' => 'Обавести при прављењу свих нових страна и измена страна.', + 'watch_desc_updates_page' => 'Обавести при свим изменама на страни.', + 'watch_title_comments' => 'Све измене странице и коментари', + 'watch_desc_comments' => 'Обавести за све нове стране, измене страна и новим коментарима.', + 'watch_desc_comments_page' => 'Обавести при измени стране и новим коментарима.', + 'watch_change_default' => 'Измени подразумевана подешавања обавештавања', + 'watch_detail_ignore' => 'Игнорисање обавештења', + 'watch_detail_new' => 'Праћење нових страна', + 'watch_detail_updates' => 'Праћење нових страна и измена', + 'watch_detail_comments' => 'Праћење нових страна, измена и коментара', + 'watch_detail_parent_book' => 'Праћење кроз родитељску књигу', + 'watch_detail_parent_book_ignore' => 'Игнорисање кроз родитељску књигу', + 'watch_detail_parent_chapter' => 'Праћење кроз родитељско поглавље', + 'watch_detail_parent_chapter_ignore' => 'Игнорисање кроз родитељско поглавље', ]; diff --git a/lang/sr/errors.php b/lang/sr/errors.php index 55ba90a5c7a..38928ccc6e9 100644 --- a/lang/sr/errors.php +++ b/lang/sr/errors.php @@ -9,127 +9,127 @@ 'permissionJson' => 'Немате овлашћење да извршите ову акцију.', // Auth - 'error_user_exists_different_creds' => 'Корисник са е-мејл адресом :email већ постоји са другим приступним подацима.', - 'auth_pre_register_theme_prevention' => 'User account could not be registered for the provided details', - 'email_already_confirmed' => 'Email has already been confirmed, Try logging in.', - 'email_confirmation_invalid' => 'This confirmation token is not valid or has already been used, Please try registering again.', - 'email_confirmation_expired' => 'The confirmation token has expired, A new confirmation email has been sent.', - 'email_confirmation_awaiting' => 'The email address for the account in use needs to be confirmed', - 'ldap_fail_anonymous' => 'LDAP access failed using anonymous bind', - 'ldap_fail_authed' => 'LDAP access failed using given dn & password details', - 'ldap_extension_not_installed' => 'LDAP PHP extension not installed', - 'ldap_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed', - 'saml_already_logged_in' => 'Already logged in', - 'saml_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system', - 'saml_invalid_response_id' => 'The request from the external authentication system is not recognised by a process started by this application. Navigating back after a login could cause this issue.', - 'saml_fail_authed' => 'Login using :system failed, system did not provide successful authorization', - 'oidc_already_logged_in' => 'Already logged in', - 'oidc_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system', - 'oidc_fail_authed' => 'Login using :system failed, system did not provide successful authorization', - 'social_no_action_defined' => 'No action defined', - 'social_login_bad_response' => "Error received during :socialAccount login: \n:error", - 'social_account_in_use' => 'This :socialAccount account is already in use, Try logging in via the :socialAccount option.', - 'social_account_email_in_use' => 'The email :email is already in use. If you already have an account you can connect your :socialAccount account from your profile settings.', - 'social_account_existing' => 'This :socialAccount is already attached to your profile.', - 'social_account_already_used_existing' => 'This :socialAccount account is already used by another user.', - 'social_account_not_used' => 'This :socialAccount account is not linked to any users. Please attach it in your profile settings. ', - 'social_account_register_instructions' => 'If you do not yet have an account, You can register an account using the :socialAccount option.', - 'social_driver_not_found' => 'Social driver not found', - 'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.', - 'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.', - 'login_user_not_found' => 'A user for this action could not be found.', + 'error_user_exists_different_creds' => 'Корисник са адресом е-поште :email већ постоји са другим приступним подацима.', + 'auth_pre_register_theme_prevention' => 'Кориснички налог није могао бити регистрован са достављеним подацима', + 'email_already_confirmed' => 'Е-пошта је већ потврђена. Покушајте да се пријавите.', + 'email_confirmation_invalid' => 'Овај потврдни токен није исправан или је већ искоришћен. Покушајте да се поново региструјете.', + 'email_confirmation_expired' => 'Потврдни токен је истекао. Послата је нова е-порука за потврду.', + 'email_confirmation_awaiting' => 'Адреса е-поште за налог у употреби мора бити потврђен', + 'ldap_fail_anonymous' => 'LDAP приступ није успео користећи анонимно спајање', + 'ldap_fail_authed' => 'LDAP приступ није успео са наведеним dn и лозинка подацима', + 'ldap_extension_not_installed' => 'LDAP PHP проширење није инсталирано', + 'ldap_cannot_connect' => 'Није могуће повезати се на ldap сервер. Иницијално повезивање није успело', + 'saml_already_logged_in' => 'Већ пријављен', + 'saml_no_email_address' => 'Нисмо могли да пронађемо адресу е-поште за овог корисника у достављеним подацима од стране екстерног система за аутентификацију', + 'saml_invalid_response_id' => 'Захтев од екстерног система за аутентификацију није препознат од стране процеса започетког овом апликацијом. Повратак након пријаве може бити узрок овог проблема.', + 'saml_fail_authed' => 'Пријава користећи :system није успела, систем није доставио успешну ауторизацију', + 'oidc_already_logged_in' => 'Већ пријављен', + 'oidc_no_email_address' => 'Нисмо могли да пронађемо адресу е-поште за овог корисника у достављеним подацима од стране екстерног система за аутентификацију', + 'oidc_fail_authed' => 'Пријава користећи :system није успела, систем није доставио успешну ауторизацију', + 'social_no_action_defined' => 'Није дефинисана радња', + 'social_login_bad_response' => "Добијена је грешка током :socialAccount пријаве: \n:error", + 'social_account_in_use' => 'Овај :socialAccount налог је већ у употреби. Покушајте пријаву са :socialAccount опцијом.', + 'social_account_email_in_use' => 'Ова е-пошта :email је већ у употреби. Ако већ имате налог можете повезати ваш :socialAccount налог у поставкама вашег профила.', + 'social_account_existing' => 'Овај :socialAccount је већ повезан са вашим профилом.', + 'social_account_already_used_existing' => 'Овај :socialAccount налог је већ у употреби од стране другог корисника.', + 'social_account_not_used' => 'Овај :socialAccount налог није повезан ни са једним корисником. Молим вас повежите га у поставкама вашег профила. ', + 'social_account_register_instructions' => 'Ако већ немате налог, можете регистровати налог користећи :socialAccount опцију.', + 'social_driver_not_found' => 'Друштвени прикључак није пронађен', + 'social_driver_not_configured' => 'Ваше поставке за :socialAccount нису исправно подешене.', + 'invite_token_expired' => 'Ова веза позивнице је истекла. Можете покушати да поништите лозинку вашег налога.', + 'login_user_not_found' => 'Корисник за ову радњу није могао бити пронађен.', // System - 'path_not_writable' => 'File path :filePath could not be uploaded to. Ensure it is writable to the server.', - 'cannot_get_image_from_url' => 'Cannot get image from :url', - 'cannot_create_thumbs' => 'The server cannot create thumbnails. Please check you have the GD PHP extension installed.', - 'server_upload_limit' => 'The server does not allow uploads of this size. Please try a smaller file size.', - 'server_post_limit' => 'The server cannot receive the provided amount of data. Try again with less data or a smaller file.', - 'uploaded' => 'The server does not allow uploads of this size. Please try a smaller file size.', + 'path_not_writable' => 'На путању :filePath није се могло отпремити. Потврдите да је уписива на серверу.', + 'cannot_get_image_from_url' => 'Није могуће добити слику из :url', + 'cannot_create_thumbs' => 'Сервер не може да прави сличице. Молим вас проверите да је GD PHP проширење инсталирано.', + 'server_upload_limit' => 'Сервер не дозвољава отпремање ове величине. Молим вас покушајте са мањом датотеком.', + 'server_post_limit' => 'Сервер не може да прими достављену количину података. Покушајте поново са мање података или са мањом датотеком.', + 'uploaded' => 'Сервер не дозвољава отпремање ове величине. Молим вас покушајте са мањом датотеком.', // Drawing & Images - 'image_upload_error' => 'An error occurred uploading the image', - 'image_upload_type_error' => 'The image type being uploaded is invalid', - 'image_upload_replace_type' => 'Image file replacements must be of the same type', - 'image_upload_memory_limit' => 'Failed to handle image upload and/or create thumbnails due to system resource limits.', - 'image_thumbnail_memory_limit' => 'Failed to create image size variations due to system resource limits.', - 'image_gallery_thumbnail_memory_limit' => 'Failed to create gallery thumbnails due to system resource limits.', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'image_upload_error' => 'Појавила се грешка током отпремања датотеке', + 'image_upload_type_error' => 'Тип датотеке слике која се отпрема није исправна', + 'image_upload_replace_type' => 'Датотека заменске слике мора бити истог типа', + 'image_upload_memory_limit' => 'Неуспело завршавање отпремања слика и/или прављења сличица због ограничења системских ресурса.', + 'image_thumbnail_memory_limit' => 'Није успело прављење варијација величина слике због ограничења системских ресурса.', + 'image_gallery_thumbnail_memory_limit' => 'Није успело прављење сличица галерије због ограничења системских ресурса.', + 'drawing_data_not_found' => 'Цртеж није могао бити учитан. Датотека цртежа можда више не постоји или ви можда немате дозволе да јој приступите.', // Attachments - 'attachment_not_found' => 'Attachment not found', - 'attachment_upload_error' => 'An error occurred uploading the attachment file', + 'attachment_not_found' => 'Прилог није пронађен', + 'attachment_upload_error' => 'Појавила се грешка током отпремања датотеке прилога', // Pages - 'page_draft_autosave_fail' => 'Failed to save draft. Ensure you have internet connection before saving this page', - 'page_draft_delete_fail' => 'Failed to delete page draft and fetch current page saved content', - 'page_custom_home_deletion' => 'Cannot delete a page while it is set as a homepage', + 'page_draft_autosave_fail' => 'Није успело чување нацрта. Потврдите да имате везу са интернетом пре снимања ове стране', + 'page_draft_delete_fail' => 'Није успело брисање нацрта стране и добављања сачуваног садржаја', + 'page_custom_home_deletion' => 'Није могуће брисање стране док је она подешена као почетна', // Entities - 'entity_not_found' => 'Entity not found', - 'bookshelf_not_found' => 'Shelf not found', + 'entity_not_found' => 'Ентитет није пронађен', + 'bookshelf_not_found' => 'Полица није пронађена', 'book_not_found' => 'Књига није пронађена', 'page_not_found' => 'Страница није пронађена', 'chapter_not_found' => 'Поглавље није пронађено', 'selected_book_not_found' => 'Одабрана књига није пронађена', - 'selected_book_chapter_not_found' => 'The selected Book or Chapter was not found', + 'selected_book_chapter_not_found' => 'Одабрана књига или поглавље није пронађено', 'guests_cannot_save_drafts' => 'Гости не могу сачувати нацрте', // Users 'users_cannot_delete_only_admin' => 'Не можете обрисати јединог администратора', 'users_cannot_delete_guest' => 'Не можете обрисати госта', - 'users_could_not_send_invite' => 'Could not create user since invite email failed to send', + 'users_could_not_send_invite' => 'Корисник није могао бити направљен јер позивница е-поруком није послата', // Roles 'role_cannot_be_edited' => 'Ова улога се не може мењати', 'role_system_cannot_be_deleted' => 'Ово је системска улога и не може се мењати', - 'role_registration_default_cannot_delete' => 'This role cannot be deleted while set as the default registration role', - 'role_cannot_remove_only_admin' => 'This user is the only user assigned to the administrator role. Assign the administrator role to another user before attempting to remove it here.', + 'role_registration_default_cannot_delete' => 'Ова улога се не може обрисати док је подешена као подразумевана улога за регистрацију', + 'role_cannot_remove_only_admin' => 'Овај корисник је једини коме је додељена улога администратора. Доделите ову улогу другом кориснику пре покушаја да га уклоните овде.', // Comments - 'comment_list' => 'An error occurred while fetching the comments.', - 'cannot_add_comment_to_draft' => 'You cannot add comments to a draft.', - 'comment_add' => 'An error occurred while adding / updating the comment.', - 'comment_delete' => 'An error occurred while deleting the comment.', - 'empty_comment' => 'Cannot add an empty comment.', + 'comment_list' => 'Појавила се грешка током добављања коментара.', + 'cannot_add_comment_to_draft' => 'Не можете додати коментаре нацрту.', + 'comment_add' => 'Појавила се грешка током додавања / измене коментара.', + 'comment_delete' => 'Појавила се грешка током брисања коментара.', + 'empty_comment' => 'Није могуће додати празан коментар.', // Error pages - '404_page_not_found' => 'Page Not Found', - 'sorry_page_not_found' => 'Sorry, The page you were looking for could not be found.', - 'sorry_page_not_found_permission_warning' => 'If you expected this page to exist, you might not have permission to view it.', - 'image_not_found' => 'Image Not Found', - 'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.', - 'image_not_found_details' => 'If you expected this image to exist it might have been deleted.', - 'return_home' => 'Return to home', + '404_page_not_found' => 'Страна није пронађена', + 'sorry_page_not_found' => 'Извините, страна коју сте тражили није могла бити пронађена.', + 'sorry_page_not_found_permission_warning' => 'Ако сте очекивали да ова страна постоји, можда немате дозволу да је прегледате.', + 'image_not_found' => 'Слика није пронађена', + 'image_not_found_subtitle' => 'Извините, слика коју сте тражили није могла бити пронађена.', + 'image_not_found_details' => 'Ако сте очекивали да ова слика постоји, можда је обрисана.', + 'return_home' => 'Повратак на почетну', 'error_occurred' => 'Догодила се грешка', - 'app_down' => ':appName is down right now', - 'back_soon' => 'It will be back up soon.', + 'app_down' => ':appName тренутно није дотупно', + 'back_soon' => 'Вратиће се ускоро.', // Import - 'import_zip_cant_read' => 'Could not read ZIP file.', - 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', - 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', - 'import_validation_failed' => 'Import ZIP failed to validate with errors:', - 'import_zip_failed_notification' => 'Failed to import ZIP file.', - 'import_perms_books' => 'You are lacking the required permissions to create books.', - 'import_perms_chapters' => 'You are lacking the required permissions to create chapters.', - 'import_perms_pages' => 'You are lacking the required permissions to create pages.', - 'import_perms_images' => 'You are lacking the required permissions to create images.', - 'import_perms_attachments' => 'You are lacking the required permission to create attachments.', + 'import_zip_cant_read' => 'Није се могла прочитати ZIP датотека.', + 'import_zip_cant_decode_data' => 'НИје се могла пронаћи и декодовати ZIP data.json садржај.', + 'import_zip_no_data' => 'Подаци у ZIP датотеци немају очекивани садржај књиге, поглавља или стране.', + 'import_zip_data_too_large' => 'ZIP data.json садржај превазилази максималну величину за отпремање подешену у апликацији.', + 'import_validation_failed' => 'Увоз ZIP-а није прошао потврду са овим грешкама:', + 'import_zip_failed_notification' => 'Неуспео увоз ZIP датотеке.', + 'import_perms_books' => 'Недостају вам неопходне дозволе да правите књиге.', + 'import_perms_chapters' => 'Недостају вам неопходне дозволе да правите поглавља.', + 'import_perms_pages' => 'Недостају вам неопходне дозволе да правите стране.', + 'import_perms_images' => 'Недостају вам неопходне дозволе да правите слике.', + 'import_perms_attachments' => 'Недостају вам неопходне дозволе да правите прилоге.', // API errors - 'api_no_authorization_found' => 'No authorization token found on the request', - 'api_bad_authorization_format' => 'An authorization token was found on the request but the format appeared incorrect', - 'api_user_token_not_found' => 'No matching API token was found for the provided authorization token', - 'api_incorrect_token_secret' => 'The secret provided for the given used API token is incorrect', - 'api_user_no_api_permission' => 'The owner of the used API token does not have permission to make API calls', - 'api_user_token_expired' => 'The authorization token used has expired', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_no_authorization_found' => 'Није пронађен токен за ауторизацију у захтеву', + 'api_bad_authorization_format' => 'Токен за ауторизацију је пронађен у захтеву али његов формат делује неисправан', + 'api_user_token_not_found' => 'Није пронађен одговарајући API токен за достављени токен ауторизације', + 'api_incorrect_token_secret' => 'Достављена тајна за пружени коришћени API токен није исправна', + 'api_user_no_api_permission' => 'Власник коришћеног API токена нема дозволе да упућује API позиве', + 'api_user_token_expired' => 'Коришћени токен за ауторизацију је истекао', + 'api_cookie_auth_only_get' => 'Дозвољени су само GET захтеви када се користи API са аутентификацијом заснованом на колачићима', // Settings & Maintenance - 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', + 'maintenance_test_email_failure' => 'Враћена је грешка током слања пробне е-поруке:', // HTTP errors - 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', + 'http_ssr_url_no_match' => 'Адреса се не подудара са подешеном за дозвољене SSR домаћине', ]; diff --git a/lang/sr/notifications.php b/lang/sr/notifications.php index 4cc499fdd40..f25c7038d9d 100644 --- a/lang/sr/notifications.php +++ b/lang/sr/notifications.php @@ -10,13 +10,13 @@ 'new_page_intro' => 'Нова страница је креирана у :appName:', 'updated_page_subject' => 'Ажурирана страница: :pageName', 'updated_page_intro' => 'Страница је ажурирана у :appName:', - 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'updated_page_debounce' => 'Да би смо спречили масовна обавештења, неко време вам неће бити слата обавештења за измене ове стране од стране истог уредника.', + 'comment_mention_subject' => 'Поменути сте у коментару на страни: :pageName', + 'comment_mention_intro' => 'Поменути сте у коментару на :appName:', 'detail_page_name' => 'Назив странице:', 'detail_page_path' => 'Путања странице:', - 'detail_commenter' => 'Commenter:', + 'detail_commenter' => 'Коментатор:', 'detail_comment' => 'Коментар:', 'detail_created_by' => 'Креирао/ла:', 'detail_updated_by' => 'Отпремио/ла:', @@ -24,6 +24,6 @@ 'action_view_comment' => 'Погледај коментар', 'action_view_page' => 'Погледај страницу', - 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', - 'footer_reason_link' => 'your notification preferences', + 'footer_reason' => 'Ово обавештење вам је послато зато што :link покрива ове типове активности за ову ставку.', + 'footer_reason_link' => 'ваше преференце за обавештавања', ]; diff --git a/lang/sr/pagination.php b/lang/sr/pagination.php index 85bd12fc319..ce1a32bcf08 100644 --- a/lang/sr/pagination.php +++ b/lang/sr/pagination.php @@ -6,7 +6,7 @@ */ return [ - 'previous' => '« Previous', - 'next' => 'Next »', + 'previous' => '« Претходна', + 'next' => 'Следећа »', ]; diff --git a/lang/sr/passwords.php b/lang/sr/passwords.php index b408f3c2fda..1c961f6973c 100644 --- a/lang/sr/passwords.php +++ b/lang/sr/passwords.php @@ -6,10 +6,10 @@ */ return [ - 'password' => 'Passwords must be at least eight characters and match the confirmation.', - 'user' => "We can't find a user with that e-mail address.", - 'token' => 'The password reset token is invalid for this email address.', - 'sent' => 'We have e-mailed your password reset link!', - 'reset' => 'Your password has been reset!', + 'password' => 'Лозинке морају имати најмање осам карактера и да се поклапају са потврдом.', + 'user' => "Не можемо да пронађемо корисника са том адресом е-поште.", + 'token' => 'Токен за поништавање лозинке је неисправан за ову адресу е-поште.', + 'sent' => 'Послали смо вам везу за поништавање лозинке е-поштом!', + 'reset' => 'Ваша лозинка је поништена!', ]; diff --git a/lang/sr/preferences.php b/lang/sr/preferences.php index f4459d738e4..a5b885b19b4 100644 --- a/lang/sr/preferences.php +++ b/lang/sr/preferences.php @@ -5,48 +5,48 @@ */ return [ - 'my_account' => 'My Account', + 'my_account' => 'Мој налог', - 'shortcuts' => 'Shortcuts', - 'shortcuts_interface' => 'UI Shortcut Preferences', - 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', - 'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.', - 'shortcuts_toggle_label' => 'Keyboard shortcuts enabled', - 'shortcuts_section_navigation' => 'Navigation', - 'shortcuts_section_actions' => 'Common Actions', - 'shortcuts_save' => 'Save Shortcuts', - 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', - 'shortcuts_update_success' => 'Shortcut preferences have been updated!', - 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + 'shortcuts' => 'Пречице', + 'shortcuts_interface' => 'Подешавања пречица интерфејса', + 'shortcuts_toggle_desc' => 'Овде можете да омогућите или онемогућите пречице тастатуре интерфејса система, које се користе за навигацију и радње.', + 'shortcuts_customize_desc' => 'Можете да прилагодите сваку пречицу испод. Само притисните жељену комбинацију тастера након одабира уноса за пречицу.', + 'shortcuts_toggle_label' => 'Пречице тастатуре се омогућене', + 'shortcuts_section_navigation' => 'Навигација', + 'shortcuts_section_actions' => 'Уобичајене радње', + 'shortcuts_save' => 'Сачувај пречице', + 'shortcuts_overlay_desc' => 'Напомена: Када су пречице омогућене помоћни приказ је доступан притиском на "?" што ће нагласити доступне пречице за радње које су тренутно видљиве на екрану.', + 'shortcuts_update_success' => 'Преференце пречица су ажуриране!', + 'shortcuts_overview_desc' => 'Управљајте пречицама тастатуре које можете да користите за навигацију корисничким интерфејсом.', - 'notifications' => 'Notification Preferences', - 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', - 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', - 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', - 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', - 'notifications_save' => 'Save Preferences', - 'notifications_update_success' => 'Notification preferences have been updated!', - 'notifications_watched' => 'Watched & Ignored Items', - 'notifications_watched_desc' => 'Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + 'notifications' => 'Подешавања обавештавања', + 'notifications_desc' => 'Контролишите обавештења е-поштом која добијате када се изврши одређена активност у оквиру система.', + 'notifications_opt_own_page_changes' => 'Обавести ме о изменама страница чији сам власник', + 'notifications_opt_own_page_comments' => 'Обавести ме о коментарима на странама чији сам власник', + 'notifications_opt_comment_mentions' => 'Обавести ме када сам поменут у коментару', + 'notifications_opt_comment_replies' => 'Обавести ме о одговорима на моје коментаре', + 'notifications_save' => 'Сачувај подешавања', + 'notifications_update_success' => 'Преференце обавештења су ажуриране!', + 'notifications_watched' => 'Праћене и игнорисане ставке', + 'notifications_watched_desc' => 'Испод су ставке над којима су примењене прилагођене преференце праћења. Да би сте ажурирали ваше преференце за ове, погледајте ставку па затим пронађите опције праћења у траци са стране.', - 'auth' => 'Access & Security', - 'auth_change_password' => 'Change Password', - 'auth_change_password_desc' => 'Change the password you use to log-in to the application. This must be at least 8 characters long.', - 'auth_change_password_success' => 'Password has been updated!', + 'auth' => 'Приступ и безбедност', + 'auth_change_password' => 'Промени лозинку', + 'auth_change_password_desc' => 'Промените лозинку коју користите за пријављивање у апликацију. Она мора бити дугачка најмање 8 карактера.', + 'auth_change_password_success' => 'Лозинка је ажурирана!', - 'profile' => 'Profile Details', - 'profile_desc' => 'Manage the details of your account which represents you to other users, in addition to details that are used for communication and system personalisation.', - 'profile_view_public' => 'View Public Profile', - 'profile_name_desc' => 'Configure your display name which will be visible to other users in the system through the activity you perform, and content you own.', - 'profile_email_desc' => 'This email will be used for notifications and, depending on active system authentication, system access.', - 'profile_email_no_permission' => 'Unfortunately you don\'t have permission to change your email address. If you want to change this, you\'d need to ask an administrator to change this for you.', - 'profile_avatar_desc' => 'Select an image which will be used to represent yourself to others in the system. Ideally this image should be square and about 256px in width and height.', - 'profile_admin_options' => 'Administrator Options', - 'profile_admin_options_desc' => 'Additional administrator-level options, like those to manage role assignments, can be found for your user account in the "Settings > Users" area of the application.', + 'profile' => 'Детаљи о профилу', + 'profile_desc' => 'Управљајте детаљима вашег налога чиме се представљате другим корисницима, поред детаља који се користе за комункацију и персонализацију система.', + 'profile_view_public' => 'Погледај јавни профил', + 'profile_name_desc' => 'Подесите ваше име за приказ које је видљво другим корисницима у систему кроз активности које извршавате и садржај који је у вашем власништу.', + 'profile_email_desc' => 'Ова е-пошта ће се користити за обавештења и, у зависности од система аутентификације, прступ систему.', + 'profile_email_no_permission' => 'На жалост немате дозволу да мењате адресу ваше е-поште. Ако желите да је промените, мораћете да замолите администратора да то уради уместо вас.', + 'profile_avatar_desc' => 'Изаберите слику која ће се користити да се представљате другима у систему. Идеално би требала бити четвртаста и око 256px у ширини и висини.', + 'profile_admin_options' => 'Администраторске опције', + 'profile_admin_options_desc' => 'Додатне опције администраторског нивоа, попут оних за управљање доделама улога, се могу пронаћи у корисничком налогу под опцијом "Поставке > Корисници".', - 'delete_account' => 'Delete Account', - 'delete_my_account' => 'Delete My Account', - 'delete_my_account_desc' => 'This will fully delete your user account from the system. You will not be able to recover this account or revert this action. Content you\'ve created, such as created pages and uploaded images, will remain.', - 'delete_my_account_warning' => 'Are you sure you want to delete your account?', + 'delete_account' => 'Обриши налог', + 'delete_my_account' => 'Обриши мој налог', + 'delete_my_account_desc' => 'Ово ће у потпуности обисати ваш кориснички налог из система. Нећете моћи да повратите овај налог или да поништите ову радњу. Садржај који сте направили, као што су странице и отпремљене слике, ће остати.', + 'delete_my_account_warning' => 'Да ли заиста желите да обришете ваш налог?', ]; diff --git a/lang/sr/settings.php b/lang/sr/settings.php index 143fdaef19e..d962a358d2d 100644 --- a/lang/sr/settings.php +++ b/lang/sr/settings.php @@ -7,24 +7,24 @@ return [ // Common Messages - 'settings' => 'Подешавања', - 'settings_save' => 'Сачувај подешавања', + 'settings' => 'Поставке', + 'settings_save' => 'Сачувај поставке', 'system_version' => 'Верзија система', 'categories' => 'Категорије', // App Settings - 'app_customization' => 'Прилгођавање', + 'app_customization' => 'Прилагођавање', 'app_features_security' => 'Својства и сигурност', 'app_name' => 'Назив апликације', 'app_name_desc' => 'Ово име се приказује у заглављу и у свим системским порукама е-поште.', 'app_name_header' => 'Прикажи назив у заглављу', - 'app_public_access' => 'Javni pristup', - 'app_public_access_desc' => 'Омогућавање ове опције ће омогућити посетиоцима, који нису пријављени, да приступе садржају у вашој Боокстак инстанци.', + 'app_public_access' => 'Јавни приступ', + 'app_public_access_desc' => 'Омогућавање ове опције ће омогућити посетиоцима, који нису пријављени, да приступе садржају у вашој Букстек инстанци.', 'app_public_access_desc_guest' => 'Приступ за јавне посетиоце може се контролисати преко корисника „Гост“.', 'app_public_access_toggle' => 'Дозволи јавни приступ', 'app_public_viewing' => 'Дозволити јавно гледање?', - 'app_secure_images' => 'Веће безбедност отпремања слика', - 'app_secure_images_toggle' => 'Омогућите већу безбедност отпремања слика', + 'app_secure_images' => 'Већа безбедност при отпремању слика', + 'app_secure_images_toggle' => 'Омогући већу безбедност при отпремању слика', 'app_secure_images_desc' => 'Из разлога перформанси, све слике су јавне. Ова опција додаје насумичан низ који је тешко погодити испред Урл-ова слике. Уверите се да индекси директоријума нису омогућени да бисте спречили лак приступ.', 'app_default_editor' => 'Подразумевани уређивач страница', 'app_default_editor_desc' => 'Изаберите који уређивач ће се подразумевано користити приликом уређивања нових страница. Ово се може заменити на нивоу странице где дозволе дозвољавају.', @@ -75,36 +75,36 @@ 'reg_confirm_restrict_domain_placeholder' => 'Нема постављених ограничења', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', - 'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.', - 'sorting_rules' => 'Sort Rules', - 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', - 'sort_rule_assigned_to_x_books' => 'Assigned to :count Book|Assigned to :count Books', - 'sort_rule_create' => 'Create Sort Rule', - 'sort_rule_edit' => 'Edit Sort Rule', - 'sort_rule_delete' => 'Delete Sort Rule', - 'sort_rule_delete_desc' => 'Remove this sort rule from the system. Books using this sort will revert to manual sorting.', - 'sort_rule_delete_warn_books' => 'This sort rule is currently used on :count book(s). Are you sure you want to delete this?', - 'sort_rule_delete_warn_default' => 'This sort rule is currently used as the default for books. Are you sure you want to delete this?', - 'sort_rule_details' => 'Sort Rule Details', - 'sort_rule_details_desc' => 'Set a name for this sort rule, which will appear in lists when users are selecting a sort.', - 'sort_rule_operations' => 'Sort Operations', - 'sort_rule_operations_desc' => 'Configure the sort actions to be performed by moving them from the list of available operations. Upon use, the operations will be applied in order, from top to bottom. Any changes made here will be applied to all assigned books upon save.', - 'sort_rule_available_operations' => 'Available Operations', - 'sort_rule_available_operations_empty' => 'No operations remaining', - 'sort_rule_configured_operations' => 'Configured Operations', - 'sort_rule_configured_operations_empty' => 'Drag/add operations from the "Available Operations" list', - 'sort_rule_op_asc' => '(Asc)', - 'sort_rule_op_desc' => '(Desc)', - 'sort_rule_op_name' => 'Name - Alphabetical', - 'sort_rule_op_name_numeric' => 'Name - Numeric', - 'sort_rule_op_created_date' => 'Created Date', - 'sort_rule_op_updated_date' => 'Updated Date', - 'sort_rule_op_chapters_first' => 'Chapters First', - 'sort_rule_op_chapters_last' => 'Chapters Last', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sorting' => 'Спискови и разврставање', + 'sorting_book_default' => 'Подразумевано правило разврставања', + 'sorting_book_default_desc' => 'Одаберите подразумевано правило разврставање за нове књиге. Ово неће утицати на постојеће књиге и може бити прерађено за сваку књигу.', + 'sorting_rules' => 'Правила разврставања', + 'sorting_rules_desc' => 'Ово су предефинисане операције разврставања које се могу применити на садржај у систему.', + 'sort_rule_assigned_to_x_books' => 'Додељено :count књизи|Додељено :count књигама', + 'sort_rule_create' => 'Направи правило разврставања', + 'sort_rule_edit' => 'Измени правило разврставања', + 'sort_rule_delete' => 'Обриши правило разврставања', + 'sort_rule_delete_desc' => 'Уклоните ово правило разврставања из система. Књиге које користе ово разврставање ће се вратити на ручно разврставање.', + 'sort_rule_delete_warn_books' => 'Ово правило разврставања тренутно користе :count књиге. Да ли заиста желите да обришете ово?', + 'sort_rule_delete_warn_default' => 'Ово правило разврставања се тренутно користи као подразумевано за књиге. Да ли заиста желите да обришете ово?', + 'sort_rule_details' => 'Детаљи правила разврставања', + 'sort_rule_details_desc' => 'Подесите назив за ово правило, које ће се појавити у списковима када корисници бирају разврставање.', + 'sort_rule_operations' => 'Операције разврставања', + 'sort_rule_operations_desc' => 'Конфигуришите радње разврставања за извршавање њиховим премештањем из списка доступних операција. Након употребе, операције ће се применити редом, од врха ка дну. Све промене извршене овде ће се применити на свим додељеним књигама након снимања.', + 'sort_rule_available_operations' => 'Доступне операције', + 'sort_rule_available_operations_empty' => 'Нема преосталих операција', + 'sort_rule_configured_operations' => 'Подешене операције', + 'sort_rule_configured_operations_empty' => 'Превуците/додајте операције са списка "Доступне операције"', + 'sort_rule_op_asc' => '(раст)', + 'sort_rule_op_desc' => '(опад)', + 'sort_rule_op_name' => 'Назив - азбучно', + 'sort_rule_op_name_numeric' => 'Назив - нумеричко', + 'sort_rule_op_created_date' => 'Датум прављења', + 'sort_rule_op_updated_date' => 'Датум ажурирања', + 'sort_rule_op_chapters_first' => 'Прво поглавља', + 'sort_rule_op_chapters_last' => 'Последње поглавља', + 'sorting_page_limits' => 'Ограничења приказа по страници', + 'sorting_page_limits_desc' => 'Подесите колико ставки се приказује по страни на разним списковима у систему. Типично мањи број ће пружити боље перформансе, док ће већи избећи потребу листање вишеструко страна. Препоручује се коришћење броја дељивог са 6.', // Maintenance settings 'maint' => 'Одржавање', @@ -113,68 +113,68 @@ 'maint_delete_images_only_in_revisions' => 'Такође избришите слике које постоје само у старим ревизијама странице', 'maint_image_cleanup_run' => 'Покрени чишћење', 'maint_image_cleanup_warning' => ':count пронађене су потенцијално неискоришћене слике. Да ли сте сигурни да желите да избришете ове слике?', - 'maint_image_cleanup_success' => ':count potentially unused images found and deleted!', - 'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!', - 'maint_send_test_email' => 'Send a Test Email', - 'maint_send_test_email_desc' => 'This sends a test email to your email address specified in your profile.', - 'maint_send_test_email_run' => 'Send test email', - 'maint_send_test_email_success' => 'Email sent to :address', - 'maint_send_test_email_mail_subject' => 'Test Email', - 'maint_send_test_email_mail_greeting' => 'Email delivery seems to work!', - 'maint_send_test_email_mail_text' => 'Congratulations! As you received this email notification, your email settings seem to be configured properly.', - 'maint_recycle_bin_desc' => 'Deleted shelves, books, chapters & pages are sent to the recycle bin so they can be restored or permanently deleted. Older items in the recycle bin may be automatically removed after a while depending on system configuration.', - 'maint_recycle_bin_open' => 'Open Recycle Bin', - 'maint_regen_references' => 'Regenerate References', - 'maint_regen_references_desc' => 'This action will rebuild the cross-item reference index within the database. This is usually handled automatically but this action can be useful to index old content or content added via unofficial methods.', - 'maint_regen_references_success' => 'Reference index has been regenerated!', - 'maint_timeout_command_note' => 'Note: This action can take time to run, which can lead to timeout issues in some web environments. As an alternative, this action be performed using a terminal command.', + 'maint_image_cleanup_success' => ':count потенцијално некоришћених слика је пронађено и обрисано!', + 'maint_image_cleanup_nothing_found' => 'Нису пронађене некоришћене слике. Ништа није обрисано!', + 'maint_send_test_email' => 'Пошаљи пробну е-поруку', + 'maint_send_test_email_desc' => 'Ово шаље пробну е-поруку на вашу адресу е-поште наведену у вашем профилу.', + 'maint_send_test_email_run' => 'Пошаљи пробну е-поруку', + 'maint_send_test_email_success' => 'Е-порука послата на :address', + 'maint_send_test_email_mail_subject' => 'Пробна порука', + 'maint_send_test_email_mail_greeting' => 'Чини се да достава е-порука функционише!', + 'maint_send_test_email_mail_text' => 'Честитамо! С обзиром да сте добили ово обавештење е-поруком, чини се да су ваше поставке исправно подешене.', + 'maint_recycle_bin_desc' => 'Обрисане полице, књиге, поглављи и стране се шаљу у канту за отпатке да би се могле повратити или трајно обрисати. Старије ставке у канти могу се аутоматски уклонити након неког времена у зависности од подешавања система.', + 'maint_recycle_bin_open' => 'Отвори канту', + 'maint_regen_references' => 'Регенериши референце', + 'maint_regen_references_desc' => 'Ова радња ће поново изградити индекс референце међу ставкама унутар базе података. Ово се обчно решава аутоматски али ова радња може бити корисна за индексацију старог садржаја или садржаја додатог кроз незваничне начине.', + 'maint_regen_references_success' => 'Индекс референци је регенерисан!', + 'maint_timeout_command_note' => 'Напомена: Овој радњи треба времена да се изврши, што може довести до проблема са истеком времена чекања у неким веб окружењима. Као алтернатива, ова радња се може извршити користећи команду у терминалу.', // Recycle Bin - 'recycle_bin' => 'Recycle Bin', - 'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.', - 'recycle_bin_deleted_item' => 'Deleted Item', - 'recycle_bin_deleted_parent' => 'Parent', - 'recycle_bin_deleted_by' => 'Deleted By', - 'recycle_bin_deleted_at' => 'Deletion Time', - 'recycle_bin_permanently_delete' => 'Permanently Delete', - 'recycle_bin_restore' => 'Restore', - 'recycle_bin_contents_empty' => 'The recycle bin is currently empty', - 'recycle_bin_empty' => 'Empty Recycle Bin', - 'recycle_bin_empty_confirm' => 'This will permanently destroy all items in the recycle bin including content contained within each item. Are you sure you want to empty the recycle bin?', - 'recycle_bin_destroy_confirm' => 'This action will permanently delete this item from the system, along with any child elements listed below, and you will not be able to restore this content. Are you sure you want to permanently delete this item?', - 'recycle_bin_destroy_list' => 'Items to be Destroyed', - 'recycle_bin_restore_list' => 'Items to be Restored', - 'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.', - 'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.', - 'recycle_bin_restore_parent' => 'Restore Parent', - 'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.', - 'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.', + 'recycle_bin' => 'Канта за отпатке', + 'recycle_bin_desc' => 'Одавде можете да вратите ставке које су обрисане или изабрати да их трајно уклоните из система. Овај списак није филтриран за разлику од сличних слискова активности у систему где су примењени филтери дозвола.', + 'recycle_bin_deleted_item' => 'Обрисана ставка', + 'recycle_bin_deleted_parent' => 'Родитељ', + 'recycle_bin_deleted_by' => 'Избрисао', + 'recycle_bin_deleted_at' => 'Време брисања', + 'recycle_bin_permanently_delete' => 'Обриши трајно', + 'recycle_bin_restore' => 'Поврати', + 'recycle_bin_contents_empty' => 'Канта је тренутно празна', + 'recycle_bin_empty' => 'Испразни канту', + 'recycle_bin_empty_confirm' => 'Ово ће трајно уништити све ставке у канти укључујући садржај унутар сваке ставке. Да ли заиста желите да испразните канту за отпатке?', + 'recycle_bin_destroy_confirm' => 'Ова радња ће трајно обрисати ову ставку из система, заједно са наследним елементима наведеним испод, и нећете моћи да вратите садржај. Да ли заиста желите да трајно обришете ову ставку?', + 'recycle_bin_destroy_list' => 'Ставке за уништавање', + 'recycle_bin_restore_list' => 'Ставке за опоравак', + 'recycle_bin_restore_confirm' => 'Ова радња ће вратити обрисану ставку, укључујући наследне елементе , на њихову оригиналну локацију. Ако је оригинална локација од тада обрисана, и сада се налази у канти за отпатке, родитељска ставка се такође мора вратити.', + 'recycle_bin_restore_deleted_parent' => 'Раодитељ ове ставке је такође обрисан. Ово ће остати обрисано док се не врати тај родитељ..', + 'recycle_bin_restore_parent' => 'Врати родитеља', + 'recycle_bin_destroy_notification' => 'Обрисано :count ставки укупно из канте за отпатке.', + 'recycle_bin_restore_notification' => 'Враћено :count ставки укупно из канте за отпатке.', // Audit Log - 'audit' => 'Audit Log', - 'audit_desc' => 'This audit log displays a list of activities tracked in the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.', - 'audit_event_filter' => 'Event Filter', - 'audit_event_filter_no_filter' => 'No Filter', + 'audit' => 'Запис за ревизију', + 'audit_desc' => 'Овај запис за ревизију приказује списак активности које се прате усистему. Овај списак није филтриран за разлику од сличних слискова активности у систему где су примењени филтери дозвола.', + 'audit_event_filter' => 'Филтер догађаја', + 'audit_event_filter_no_filter' => 'Без филтера', 'audit_deleted_item' => 'Избрисана ставка', - 'audit_deleted_item_name' => 'Name: :name', + 'audit_deleted_item_name' => 'Назив: :name', 'audit_table_user' => 'Корисник', 'audit_table_event' => 'Догађај', - 'audit_table_related' => 'Related Item or Detail', + 'audit_table_related' => 'Повезана ставка или детаљ', 'audit_table_ip' => 'ИП адреса', 'audit_table_date' => 'Датум активности', - 'audit_date_from' => 'Date Range From', - 'audit_date_to' => 'Date Range To', + 'audit_date_from' => 'Опсег датума од', + 'audit_date_to' => 'Опсег датума до', // Role Settings 'roles' => 'Улоге', - 'role_user_roles' => 'User Roles', - 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => ':count user assigned|:count users assigned', - 'roles_x_permissions_provided' => ':count permission|:count permissions', - 'roles_assigned_users' => 'Assigned Users', - 'roles_permissions_provided' => 'Provided Permissions', - 'role_create' => 'Create New Role', - 'role_delete' => 'Delete Role', + 'role_user_roles' => 'Корисничке улоге', + 'roles_index_desc' => 'Улоге се користе да би се груписали корисници и доделиле дозволе за систем њиховим члановима. Када је корисник члан вишеструко група привилегије додељене ће се објединити и корисник ће наследити све способности.', + 'roles_x_users_assigned' => ':count корисник додељен|:count корисника додељено', + 'roles_x_permissions_provided' => ':count дозвола|:count дозвола', + 'roles_assigned_users' => 'Додељени корисници', + 'roles_permissions_provided' => 'Пружене дозволе', + 'role_create' => 'Направи нову улогу', + 'role_delete' => 'Обриши улогу', 'role_delete_confirm' => 'Ово ће избрисати улогу са именом \':roleName\'.', 'role_delete_users_assigned' => 'Ова улога има :userCount корисника који су јој додељени. Ако желите да мигрирате кориснике са ове улоге, изаберите нову улогу испод.', 'role_delete_no_migration' => "Немојте мигрирати кориснике", @@ -184,145 +184,145 @@ 'role_name' => 'Назив улоге', 'role_desc' => 'Кратак опис улоге', 'role_mfa_enforced' => 'Захтева вишефакторску аутентификацију', - 'role_external_auth_id' => 'External Authentication IDs', - 'role_system' => 'System Permissions', - 'role_manage_users' => 'Manage users', - 'role_manage_roles' => 'Manage roles & role permissions', - 'role_manage_entity_permissions' => 'Manage all book, chapter & page permissions', - 'role_manage_own_entity_permissions' => 'Manage permissions on own book, chapter & pages', - 'role_manage_page_templates' => 'Manage page templates', - 'role_access_api' => 'Access system API', - 'role_manage_settings' => 'Manage app settings', - 'role_export_content' => 'Export content', - 'role_import_content' => 'Import content', - 'role_editor_change' => 'Change page editor', - 'role_notifications' => 'Receive & manage notifications', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', - 'role_asset' => 'Asset Permissions', - 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', - 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', - 'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.', - 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', - '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', + 'role_external_auth_id' => 'ID-јеви екстерне аутентификације', + 'role_system' => 'Системске дозволе', + 'role_manage_users' => 'Управља корисницима', + 'role_manage_roles' => 'Управља улогама и дозволама улога', + 'role_manage_entity_permissions' => 'Управља дозволама над свим елементима', + 'role_manage_own_entity_permissions' => 'Управља дозволама над сопственим елементима', + 'role_manage_page_templates' => 'Управља шаблонима страна', + 'role_access_api' => 'Приступа системском API-ју', + 'role_manage_settings' => 'Управља поставкама апликације', + 'role_export_content' => 'Извози садржај', + 'role_import_content' => 'Увози садржај', + 'role_editor_change' => 'Мења уређивач стране', + 'role_notifications' => 'Прима и управља обавештењима', + 'role_permission_note_users_and_roles' => 'Ове дозволе ће технички такође омогућити видљивост и претрагу корисника и улога у систему.', + 'role_asset' => 'Дозволе над имовином', + 'roles_system_warning' => 'Имајте на уму да приступ било којој од дозвола изнад може да дозволи кориснику да измени сопствене привилегије или привилегије других у систему. Доделите улоге са овим дозволама само корисницима од поверења.', + 'role_asset_desc' => 'Ове дозволе конторлишу подразумевани приступ имовини унутар система. Дозволе над књигама, поглављима и странама ће прерадити ове дозволе.', + 'role_asset_admins' => 'Администраторима је аутоматски дат приступ целом садржају али ове опције могу да прикажу или сакрију опције на интерфејсу.', + 'role_asset_image_view_note' => 'Ово се односи на видљивост унутар менаџера слика. Сам приступ отпремљеним сликама зависиће од системских опције складиштења слика.', + 'role_asset_users_note' => 'Ове дозволе ће технички такође омогућити видљивост и претрагу корисника у систему.', + 'role_all' => 'Све', + 'role_own' => 'Власник', + 'role_controlled_by_asset' => 'Контролисано по имовини у којој су постављене', + 'role_controlled_by_page_delete' => 'Контролисано дозволама брисања од странице', + 'role_save' => 'Сачувај улогу', + 'role_users' => 'Корисници са овом улогом', + 'role_users_none' => 'Тренутно ниједном кориснику није додељена ова улога', // Users - 'users' => 'Users', - 'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.', - 'user_profile' => 'User Profile', - 'users_add_new' => 'Add New User', - 'users_search' => 'Search Users', - 'users_latest_activity' => 'Latest Activity', - 'users_details' => 'User Details', - 'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.', - 'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.', - 'users_role' => 'User Roles', - 'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.', - 'users_password' => 'User Password', - 'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 8 characters long.', - 'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.', - 'users_send_invite_option' => 'Send user invite email', - 'users_external_auth_id' => 'External Authentication ID', - 'users_external_auth_id_desc' => 'When an external authentication system is in use (such as SAML2, OIDC or LDAP) this is the ID which links this BookStack user to the authentication system account. You can ignore this field if using the default email-based authentication.', - 'users_password_warning' => 'Only fill the below if you would like to change the password for this user.', - 'users_system_public' => 'This user represents any guest users that visit your instance. It cannot be used to log in but is assigned automatically.', - 'users_delete' => 'Delete User', - 'users_delete_named' => 'Delete user :userName', - 'users_delete_warning' => 'This will fully delete this user with the name \':userName\' from the system.', - 'users_delete_confirm' => 'Are you sure you want to delete this user?', - 'users_migrate_ownership' => 'Migrate Ownership', - 'users_migrate_ownership_desc' => 'Select a user here if you want another user to become the owner of all items currently owned by this user.', - 'users_none_selected' => 'No user selected', - 'users_edit' => 'Edit User', - 'users_edit_profile' => 'Edit Profile', - 'users_avatar' => 'User Avatar', - 'users_avatar_desc' => 'Select an image to represent this user. This should be approx 256px square.', - 'users_preferred_language' => 'Preferred Language', - 'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.', - 'users_social_accounts' => 'Social Accounts', - 'users_social_accounts_desc' => 'View the status of the connected social accounts for this user. Social accounts can be used in addition to the primary authentication system for system access.', - 'users_social_accounts_info' => 'Here you can connect your other accounts for quicker and easier login. Disconnecting an account here does not revoke previously authorized access. Revoke access from your profile settings on the connected social account.', - 'users_social_connect' => 'Connect Account', - 'users_social_disconnect' => 'Disconnect Account', - 'users_social_status_connected' => 'Connected', - 'users_social_status_disconnected' => 'Disconnected', - 'users_social_connected' => ':socialAccount account was successfully attached to your profile.', - 'users_social_disconnected' => ':socialAccount account was successfully disconnected from your profile.', - 'users_api_tokens' => 'API Tokens', - 'users_api_tokens_desc' => 'Create and manage the access tokens used to authenticate with the BookStack REST API. Permissions for the API are managed via the user that the token belongs to.', - 'users_api_tokens_none' => 'No API tokens have been created for this user', - 'users_api_tokens_create' => 'Create Token', - 'users_api_tokens_expires' => 'Expires', - 'users_api_tokens_docs' => 'API Documentation', - 'users_mfa' => 'Multi-Factor Authentication', - '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?', + 'users' => 'Корисници', + 'users_index_desc' => 'Правите и управљајте појединачним налозима корисника унутар система. Кориснички налози се користе за пријављивање и приписивање садржаја и активности. Дозволе за приступ су примарно базиране на улогама али власништо над корисничким садржајем, између осталог, такође може да утиче на дозволе и приступ.', + 'user_profile' => 'Кориснички профил', + 'users_add_new' => 'Додај новог корисника', + 'users_search' => 'Претражи кориснике', + 'users_latest_activity' => 'Последња активност', + 'users_details' => 'Детаљи о кориснику', + 'users_details_desc' => 'Подесите име за приказ и адресу е-поште за овог корисника. Ова адреса е-поште ће се користити за пријављивање у апликацију.', + 'users_details_desc_no_email' => 'Подесите име за приказ за овог корисника како би други могли да га препознају.', + 'users_role' => 'Корисничке улоге', + 'users_role_desc' => 'Одаберите које улоге ће бити додељене кориснику. Ако је кориснику додељено више улога, дозволе од тих улога ће се сакупити и они ће добити све дозволе додељених улога.', + 'users_password' => 'Корисничка лозинка', + 'users_password_desc' => 'Подесите лозинку која се користи за пријављивање у апликацију. Она мора имати најмање 8 карактера.', + 'users_send_invite_text' => 'Можете изабрати да овом кориснику пошаљете позивницу е-поруком што ће им омогућити да самостално подесе своју лозинку. У супротном можете је сами подесити.', + 'users_send_invite_option' => 'Пошаљи кориснику позивницу е-поруком', + 'users_external_auth_id' => 'ID екстерне аутентификације', + 'users_external_auth_id_desc' => 'Када се користи екстерни систем за аутентификацију (попут SAML2, OIDC или LDAP) ово је ID који повезује овог BookStack корисника са налогом система за аутентфикацију. Можете игнорисати ово поље ако користите подразумевану аутентификацију засновану на е-пошти.', + 'users_password_warning' => 'Попуните поља испод само ако желите да промените лозинку овом кориснику.', + 'users_system_public' => 'Овај корисник представља сваког госта који посећује вашу инстанцу. Не може се користити за пријављивање али је аутоматски додељен.', + 'users_delete' => 'Обриши корисника', + 'users_delete_named' => 'Обриши корисника :userName', + 'users_delete_warning' => 'Ово ће у потпуности да обрише овог корисника са именом \':userName\' из система.', + 'users_delete_confirm' => 'Да ли заиста желите да обришете овог корисника?', + 'users_migrate_ownership' => 'Миграција власништва', + 'users_migrate_ownership_desc' => 'Овде одаберите корисника ако желите да други корисник постане власник свих ставки за које је власник овај корисник.', + 'users_none_selected' => 'Корисник није изабран', + 'users_edit' => 'Измени корисника', + 'users_edit_profile' => 'Измена профила', + 'users_avatar' => 'Аватар корисника', + 'users_avatar_desc' => 'Изаберите слику која ће да представља овог корисника. Требала би бити квадрат приближно 256px.', + 'users_preferred_language' => 'Преферирани језик', + 'users_preferred_language_desc' => 'Ова опција ће променити језик који се користи за кориснички интерфејс апликације. Ово неће утицати на било какав садржај направљен од стране корисника.', + 'users_social_accounts' => 'Налози друштвених мрежа', + 'users_social_accounts_desc' => 'Погледајте статус повезаних налога друштвених мрежа за овог корисника. Налози друштвених мрежа се могу користити за приступ систему поред примарног система за аутентификацију.', + 'users_social_accounts_info' => 'Овде можете да повежете ваше друге налоге за бржу и лакшу пријаву. Развезивање налога овде не укида претходно ауторизован приступ. Повуците приступ из поставки вашег профила на повезаном налогу друштвене мреже.', + 'users_social_connect' => 'Повежи налог', + 'users_social_disconnect' => 'Развежи налог', + 'users_social_status_connected' => 'Повезан', + 'users_social_status_disconnected' => 'Развезан', + 'users_social_connected' => ':socialAccount налог је успешно закачен на ваш профил.', + 'users_social_disconnected' => ':socialAccount налог је успешно развезан са вашег профила.', + 'users_api_tokens' => 'API токени', + 'users_api_tokens_desc' => 'Направљај и управљај токенима за приступ који се користе за аутентификацију са BookStack REST API-јем. Дозволама за API се управља кроз корисника којем припада токен.', + 'users_api_tokens_none' => 'Још ниједан API токен није направљен за овог корисника', + 'users_api_tokens_create' => 'Направи токен', + 'users_api_tokens_expires' => 'Истиче', + 'users_api_tokens_docs' => 'API документација', + 'users_mfa' => 'Вишефакторска аутентификација', + 'users_mfa_desc' => 'Подесите вишефакторску аутентификацију ка додатни слој безбедности за ваш кориснички налог.', + 'users_mfa_x_methods' => ':count начин подешен|:count начина су подешена', + 'users_mfa_configure' => 'Подеси начине', + 'users_mfa_reset' => 'Поништи начине вишефакторске аутентификације', + 'users_mfa_reset_desc' => 'Ово ће понитити и почистити све подешене начине вишефакторске аутентификације за овог корисника. Ако је вишефакторска аутентификација неопходна за било коју од његових улога, од њих ће бити затражено да подесе нови начин приликом наредне пријаве.', + 'users_mfa_reset_confirm' => 'Да ли заиста желите да поништите вишефакторску аутентификацију за овог корисника?', // API Tokens - 'user_api_token_create' => 'Create API Token', - 'user_api_token_name' => 'Name', - 'user_api_token_name_desc' => 'Give your token a readable name as a future reminder of its intended purpose.', - 'user_api_token_expiry' => 'Expiry Date', - 'user_api_token_expiry_desc' => 'Set a date at which this token expires. After this date, requests made using this token will no longer work. Leaving this field blank will set an expiry 100 years into the future.', - 'user_api_token_create_secret_message' => 'Immediately after creating this token a "Token ID" & "Token Secret" will be generated and displayed. The secret will only be shown a single time so be sure to copy the value to somewhere safe and secure before proceeding.', - 'user_api_token' => 'API Token', - 'user_api_token_id' => 'Token ID', - 'user_api_token_id_desc' => 'This is a non-editable system generated identifier for this token which will need to be provided in API requests.', - 'user_api_token_secret' => 'Token Secret', - 'user_api_token_secret_desc' => 'This is a system generated secret for this token which will need to be provided in API requests. This will only be displayed this one time so copy this value to somewhere safe and secure.', - 'user_api_token_created' => 'Token created :timeAgo', - 'user_api_token_updated' => 'Token updated :timeAgo', - 'user_api_token_delete' => 'Delete Token', - 'user_api_token_delete_warning' => 'This will fully delete this API token with the name \':tokenName\' from the system.', - 'user_api_token_delete_confirm' => 'Are you sure you want to delete this API token?', + 'user_api_token_create' => 'Направи API токен', + 'user_api_token_name' => 'Назив', + 'user_api_token_name_desc' => 'Дајте вашем токену читљив назив као будући подсетник његове сврхе.', + 'user_api_token_expiry' => 'Датум истека', + 'user_api_token_expiry_desc' => 'Подесите датум када истиче ваш токен. Након овог датума, захтеви послати користећи овај токен више неће функционисати. Остављањем овог поља празним ће подесити истек 100 година у будућности.', + 'user_api_token_create_secret_message' => 'Моментално након прављења овог токена "ID токена" и "Тајна токена" ће бити генерисани и приказани. Тајна ће бити приказана само једном зато се потрудите да ископирате вредност на неко сигурно и безбедно место пре настављања.', + 'user_api_token' => 'API токен', + 'user_api_token_id' => 'ID токена', + 'user_api_token_id_desc' => 'Ово је неизменљиви идентификатор који је генерисао систем за овај токен којег треба доставити у API захтевима.', + 'user_api_token_secret' => 'Тајна токена', + 'user_api_token_secret_desc' => 'Ово је тајна коју је генерисао систем за овај токен коју треба доставити у API захтевима. Ово ће бити приказано само једном зато ископирајте ову вредност на неко сигурно и безбедно место.', + 'user_api_token_created' => 'Токен је направљен :timeAgo', + 'user_api_token_updated' => 'Токен је ажуриран :timeAgo', + 'user_api_token_delete' => 'Обриши токен', + 'user_api_token_delete_warning' => 'Ово ће у потпуности обрисати овај API токен са називом \':tokenName\' из система.', + 'user_api_token_delete_confirm' => 'Да ли заиста желите да обришете овај API токен?', // Webhooks - 'webhooks' => 'Webhooks', - 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', - 'webhooks_create' => 'Create New Webhook', - 'webhooks_none_created' => 'No webhooks have yet been created.', - 'webhooks_edit' => 'Edit Webhook', - 'webhooks_save' => 'Save Webhook', - 'webhooks_details' => 'Webhook Details', - 'webhooks_details_desc' => 'Provide a user friendly name and a POST endpoint as a location for the webhook data to be sent to.', - 'webhooks_events' => 'Webhook Events', - 'webhooks_events_desc' => 'Select all the events that should trigger this webhook to be called.', - 'webhooks_events_warning' => 'Keep in mind that these events will be triggered for all selected events, even if custom permissions are applied. Ensure that use of this webhook won\'t expose confidential content.', - 'webhooks_events_all' => 'All system events', - 'webhooks_name' => 'Webhook Name', - 'webhooks_timeout' => 'Webhook Request Timeout (Seconds)', - 'webhooks_endpoint' => 'Webhook Endpoint', - 'webhooks_active' => 'Webhook Active', - 'webhook_events_table_header' => 'Events', - 'webhooks_delete' => 'Delete Webhook', - 'webhooks_delete_warning' => 'This will fully delete this webhook, with the name \':webhookName\', from the system.', - 'webhooks_delete_confirm' => 'Are you sure you want to delete this webhook?', - 'webhooks_format_example' => 'Webhook Format Example', - 'webhooks_format_example_desc' => 'Webhook data is sent as a POST request to the configured endpoint as JSON following the format below. The "related_item" and "url" properties are optional and will depend on the type of event triggered.', - 'webhooks_status' => 'Webhook Status', - 'webhooks_last_called' => 'Last Called:', - 'webhooks_last_errored' => 'Last Errored:', - 'webhooks_last_error_message' => 'Last Error Message:', + 'webhooks' => 'Веб закачке', + 'webhooks_index_desc' => 'Веб закачке су начин да се пошаљу подаци на екстерне УРЛ адресе када се одређене радње и догађаји одвију унутар система који дозвољава интеграцију засновану на догађајима са екстерним платформама као што су размена порука или системи за обавештавање.', + 'webhooks_x_trigger_events' => ':count окидач догађаја|:count окидача догађаја', + 'webhooks_create' => 'Направи нову веб закачку', + 'webhooks_none_created' => 'Још нису направљене веб закачке.', + 'webhooks_edit' => 'Измени веб закачку', + 'webhooks_save' => 'Сачувај веб закачку', + 'webhooks_details' => 'Детаљи веб закачке', + 'webhooks_details_desc' => 'Пружите одговарајући назив и POST крајњу тачку којој слати податке ове веб закачке.', + 'webhooks_events' => 'Догађаји веб закачке', + 'webhooks_events_desc' => 'Изаберите све догађаје који требају да окину позив за ову веб закачку.', + 'webhooks_events_warning' => 'Имајте на уму да ће ови догађаји бити окинути за све изабране догађаје, чак и када су примењене прилагођене дозволе. Обезбедите да коришћење ове веб закачке неће изложити поверљив садржај.', + 'webhooks_events_all' => 'Сви догађаји система', + 'webhooks_name' => 'Назив веб закачке', + 'webhooks_timeout' => 'Време чекања на веб закачку (секунде)', + 'webhooks_endpoint' => 'Крајња тачка веб закачке', + 'webhooks_active' => 'Веб закачка је активна', + 'webhook_events_table_header' => 'Догађаји', + 'webhooks_delete' => 'Обриши веб закачку', + 'webhooks_delete_warning' => 'Ово ће у потпуности обрисати ову веб закачку са називом \':webhookName\' из система.', + 'webhooks_delete_confirm' => 'Да ли заиста желите да обришете ову веб закачку?', + 'webhooks_format_example' => 'Пример формата веб закачке', + 'webhooks_format_example_desc' => 'Податак веб закачке се шаље као POST захтев подешеној крајњој тачки као JSON пратећи формат испод. "related_item" и "url" својства су опциона и зависиће од типе окинутог догађаја.', + 'webhooks_status' => 'Статус веб закачке', + 'webhooks_last_called' => 'Последњи пут позвана:', + 'webhooks_last_errored' => 'Последња грешка:', + 'webhooks_last_error_message' => 'Порука последње грешке:', // Licensing - 'licenses' => 'Licenses', - 'licenses_desc' => 'This page details license information for BookStack in addition to the projects & libraries that are used within BookStack. Many projects listed may only be used in a development context.', - 'licenses_bookstack' => 'BookStack License', - 'licenses_php' => 'PHP Library Licenses', - 'licenses_js' => 'JavaScript Library Licenses', - 'licenses_other' => 'Other Licenses', - 'license_details' => 'License Details', + 'licenses' => 'Лиценце', + 'licenses_desc' => 'Ова страна приказује детаљне информације о лиценци за BookStack поред самих пројеката и библиотека које се користе унутар BookStack-а. Многи пројекти наведени могу се користити само у току развоја.', + 'licenses_bookstack' => 'BookStack лиценца', + 'licenses_php' => 'Лиценца PHP библиотеке', + 'licenses_js' => 'Лиценце JavaScript библиотеке', + 'licenses_other' => 'Друге лиценце', + 'license_details' => 'Детаљи о лиценци', //! If editing translations files directly please ignore this in all //! languages apart from en. Content will be auto-copied from en. @@ -366,8 +366,9 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', - 'th' => 'ภาษาไทย', + 'th' => 'Тајландски', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/sr/validation.php b/lang/sr/validation.php index 6770c5a8015..35e6c706f93 100644 --- a/lang/sr/validation.php +++ b/lang/sr/validation.php @@ -8,113 +8,113 @@ return [ // Standard laravel validation lines - 'accepted' => 'The :attribute must be accepted.', - 'active_url' => 'The :attribute is not a valid URL.', - 'after' => 'The :attribute must be a date after :date.', - 'alpha' => 'The :attribute may only contain letters.', - 'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.', - 'alpha_num' => 'The :attribute may only contain letters and numbers.', + 'accepted' => ':attribute мора бити прихваћен.', + 'active_url' => ':attribute није исправна URL адреса.', + 'after' => ':attribute мора бити датум после :date.', + 'alpha' => ':attribute може да садржи само слова.', + 'alpha_dash' => ':attribute може да садржи само слова, бројеве, црте и подцрте.', + 'alpha_num' => ':attribute може да садржи само слова и бројеве.', 'array' => ':attribute мора бити низ.', - 'backup_codes' => 'The provided code is not valid or has already been used.', - 'before' => 'The :attribute must be a date before :date.', + 'backup_codes' => 'Достављени код није исправан или је већ искоришћен.', + 'before' => ':attribute мора бити датум пре :date.', 'between' => [ - 'numeric' => 'The :attribute must be between :min and :max.', - 'file' => 'The :attribute must be between :min and :max kilobytes.', - 'string' => 'The :attribute must be between :min and :max characters.', - 'array' => 'The :attribute must have between :min and :max items.', + 'numeric' => ':attribute мора бити између :min и :max.', + 'file' => ':attribute мора бити између :min и :max килобајта.', + 'string' => ':attribute мора бити између :min и :max карактера.', + 'array' => ':attribute мора бити између :min и :max ставки.', ], - 'boolean' => 'The :attribute field must be true or false.', - 'confirmed' => 'The :attribute confirmation does not match.', - 'date' => 'The :attribute is not a valid date.', - 'date_format' => 'The :attribute does not match the format :format.', - 'different' => 'The :attribute and :other must be different.', - 'digits' => 'The :attribute must be :digits digits.', - 'digits_between' => 'The :attribute must be between :min and :max digits.', - 'email' => 'The :attribute must be a valid email address.', - 'ends_with' => 'The :attribute must end with one of the following: :values', - 'file' => 'The :attribute must be provided as a valid file.', - 'filled' => 'The :attribute field is required.', + 'boolean' => 'Поље :attribute мора бити тачно или нетачно.', + 'confirmed' => ':attribute потврда се не подудара.', + 'date' => ':attribute није исправан датум.', + 'date_format' => ':attribute се не подудара са форматом :format.', + 'different' => ':attribute и :other се морају разликовати.', + 'digits' => ':attribute мора бити :digits цифри.', + 'digits_between' => ':attribute мора бити између :min и :max цифара.', + 'email' => ':attribute морабити исправна адреса е-поште.', + 'ends_with' => ':attribute се мора завршити са једним од следећих: :values', + 'file' => ':attribute мора бити исправна достављена датотека.', + 'filled' => ':attribute поље је неопходно.', 'gt' => [ - 'numeric' => 'The :attribute must be greater than :value.', - 'file' => 'The :attribute must be greater than :value kilobytes.', - 'string' => 'The :attribute must be greater than :value characters.', - 'array' => 'The :attribute must have more than :value items.', + 'numeric' => ':attribute мора бити веће од :value.', + 'file' => ':attribute мора бити веће од :value килобајта.', + 'string' => ':attribute мора бити веће од :value карактера.', + 'array' => ':attribute мора садржати више од :value ставки.', ], 'gte' => [ - 'numeric' => 'The :attribute must be greater than or equal :value.', - 'file' => 'The :attribute must be greater than or equal :value kilobytes.', - 'string' => 'The :attribute must be greater than or equal :value characters.', - 'array' => 'The :attribute must have :value items or more.', + 'numeric' => ':attribute мора бити веће од или једнако :value.', + 'file' => ':attribute мора бити веће од или једнако :value килобајта.', + 'string' => ':attribute мора бити веће од или једнако :value карактера.', + 'array' => ':attribute мора да садржи :value или више ставки.', ], - 'exists' => 'The selected :attribute is invalid.', - 'image' => 'The :attribute must be an image.', - 'image_extension' => 'The :attribute must have a valid & supported image extension.', - 'in' => 'The selected :attribute is invalid.', - 'integer' => 'The :attribute must be an integer.', - 'ip' => 'The :attribute must be a valid IP address.', - 'ipv4' => 'The :attribute must be a valid IPv4 address.', - 'ipv6' => 'The :attribute must be a valid IPv6 address.', - 'json' => 'The :attribute must be a valid JSON string.', + 'exists' => 'Изабрани :attribute је неисправан.', + 'image' => ':attribute мора бити слика.', + 'image_extension' => ':attribute мора да има исправну и подржану екстензију слике.', + 'in' => 'Изабрани :attribute је неисправан.', + 'integer' => ':attribute мора бити цели број.', + 'ip' => ':attribute мора бити исправна ИП адреса.', + 'ipv4' => ':attribute мора бити исправна IPv4 адреса.', + 'ipv6' => ':attribute мора бити исправна IPv6 адреса.', + 'json' => ':attribute мора бити исправна JSON ниска.', 'lt' => [ - 'numeric' => 'The :attribute must be less than :value.', - 'file' => 'The :attribute must be less than :value kilobytes.', - 'string' => 'The :attribute must be less than :value characters.', - 'array' => 'The :attribute must have less than :value items.', + 'numeric' => ':attribute мора бити мање од :value.', + 'file' => ':attribute мора бити мање од :value килобајта.', + 'string' => ':attribute мора бити мање од :value карактера.', + 'array' => ':attribute мора садржати мање од :value ставки.', ], 'lte' => [ - 'numeric' => 'The :attribute must be less than or equal :value.', - 'file' => 'The :attribute must be less than or equal :value kilobytes.', - 'string' => 'The :attribute must be less than or equal :value characters.', - 'array' => 'The :attribute must not have more than :value items.', + 'numeric' => ':attribute мора бити мање од или једнако :value.', + 'file' => ':attribute мора бити мање од или једнако :value килобајта.', + 'string' => ':attribute мора бити мање од или једнако :value карактера.', + 'array' => ':attribute не сме садржати више од :value ставки.', ], 'max' => [ - 'numeric' => 'The :attribute may not be greater than :max.', - 'file' => 'The :attribute may not be greater than :max kilobytes.', - 'string' => 'The :attribute may not be greater than :max characters.', - 'array' => 'The :attribute may not have more than :max items.', + 'numeric' => ':attribute не може бити већи од :max.', + 'file' => ':attribute не може бити већи од :max килобајта.', + 'string' => ':attribute не може бити већи од :max знакова.', + 'array' => ':attribute не може садржати више од :max ставки.', ], - 'mimes' => 'The :attribute must be a file of type: :values.', + 'mimes' => ':attribute мора бити датотека типа: :values.', 'min' => [ - 'numeric' => 'The :attribute must be at least :min.', - 'file' => 'The :attribute must be at least :min kilobytes.', - 'string' => 'The :attribute must be at least :min characters.', - 'array' => 'The :attribute must have at least :min items.', + 'numeric' => ':attribute мора бити најмање :min.', + 'file' => ':attribute мора бити најмање :min килобајта.', + 'string' => ':attribute мора бити најмање :min карактера.', + 'array' => ':attribute мора садржати најмање :min ставки.', ], - 'not_in' => 'The selected :attribute is invalid.', - 'not_regex' => 'The :attribute format is invalid.', - 'numeric' => 'The :attribute must be a number.', - 'regex' => 'The :attribute format is invalid.', - 'required' => 'The :attribute field is required.', - 'required_if' => 'The :attribute field is required when :other is :value.', - 'required_with' => 'The :attribute field is required when :values is present.', - 'required_with_all' => 'The :attribute field is required when :values is present.', - 'required_without' => 'The :attribute field is required when :values is not present.', - 'required_without_all' => 'The :attribute field is required when none of :values are present.', - 'same' => 'The :attribute and :other must match.', - 'safe_url' => 'The provided link may not be safe.', + 'not_in' => 'Изабрани :attribute је неисправан.', + 'not_regex' => ':attribute формат је неисправан.', + 'numeric' => ':attribute мора бити број.', + 'regex' => ':attribute формат је неисправан.', + 'required' => ':attribute поље је неопходно.', + 'required_if' => ':attribute поље је неопходно када :other је :value.', + 'required_with' => 'Поље :attribute је обавезно када је :values присутно.', + 'required_with_all' => 'Поље :attribute је обавезно када је :values присутно.', + 'required_without' => 'Поље :attribute је обавезно када :values није присутно.', + 'required_without_all' => 'Поље :attribute је обавезно када ниједно од :values није присутно.', + 'same' => ':attribute и :other се морају поклапати.', + 'safe_url' => 'Достављена веза можда није безбедна.', 'size' => [ - 'numeric' => 'The :attribute must be :size.', - 'file' => 'The :attribute must be :size kilobytes.', - 'string' => 'The :attribute must be :size characters.', - 'array' => 'The :attribute must contain :size items.', + 'numeric' => ':attribute мора бити :size.', + 'file' => ':attribute мора бити :size килобајта.', + 'string' => ':attribute мора бити :size карактера.', + 'array' => ':attribute мора да садржи :size ставки.', ], - 'string' => 'The :attribute must be a string.', - 'timezone' => 'The :attribute must be a valid zone.', - 'totp' => 'The provided code is not valid or has expired.', - 'unique' => 'The :attribute has already been taken.', - 'url' => 'The :attribute format is invalid.', - 'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.', + 'string' => ':attribute мора бити текст.', + 'timezone' => ':attribute мора бити исправна зона.', + 'totp' => 'Достављени код није исправан или је истекао.', + 'unique' => ':attribute је већ заузет.', + 'url' => ':attribute формат је неисправан.', + 'uploaded' => 'Датотека није могла бити отпремљена. Сервер можда не прихвата датотеке ове величине.', - 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', - 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', - 'zip_model_expected' => 'Data object expected but ":type" found.', - 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', + 'zip_file' => ':attribute мора бити референца за датотеку унутар ZIP-а.', + 'zip_file_size' => ':attribute датотеке не сме бити већи од :size MB.', + 'zip_file_mime' => ':attribute мора бити референца за датотеку типа :validTypes, пронађено :foundType.', + 'zip_model_expected' => 'Очекиван је објекат податка али је ":type" пронађен.', + 'zip_unique' => ':attribute мора бити јединствено за тип објекта унутар ZIP-а.', // Custom validation lines 'custom' => [ 'password-confirm' => [ - 'required_with' => 'Password confirmation required', + 'required_with' => 'Неопходна је потврда лозинке', ], ], diff --git a/lang/sv/settings.php b/lang/sv/settings.php index e900a86a3ba..bf9968270ab 100644 --- a/lang/sv/settings.php +++ b/lang/sv/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenska', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/th/settings.php b/lang/th/settings.php index 558d2da0f6e..af85e37c109 100644 --- a/lang/th/settings.php +++ b/lang/th/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/tk/settings.php b/lang/tk/settings.php index d03024a89d6..0e5ce84cf21 100644 --- a/lang/tk/settings.php +++ b/lang/tk/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/tr/settings.php b/lang/tr/settings.php index 39ae23f2845..ac7ba9747ae 100644 --- a/lang/tr/settings.php +++ b/lang/tr/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovence', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/uk/settings.php b/lang/uk/settings.php index 521dc9578fd..0eae5a364c7 100644 --- a/lang/uk/settings.php +++ b/lang/uk/settings.php @@ -366,8 +366,9 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', - 'th' => 'ภาษาไทย', + 'th' => 'Тайська', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/uz/settings.php b/lang/uz/settings.php index c1d3b3d46e1..ed3421c914c 100644 --- a/lang/uz/settings.php +++ b/lang/uz/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/vi/settings.php b/lang/vi/settings.php index d029fe7f329..a3d3d549155 100644 --- a/lang/vi/settings.php +++ b/lang/vi/settings.php @@ -366,6 +366,7 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', 'th' => 'ภาษาไทย', 'tr' => 'Türkçe', diff --git a/lang/zh_CN/auth.php b/lang/zh_CN/auth.php index 4c97b46ce02..6cb68aad8ee 100644 --- a/lang/zh_CN/auth.php +++ b/lang/zh_CN/auth.php @@ -8,7 +8,7 @@ 'failed' => '用户名或密码错误。', 'throttle' => '您的登录次数过多,请在:seconds秒后重试。', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => '多因子认证尝试次数过多,请在 :seconds 秒后重试。', // Login & Register 'sign_up' => '注册', diff --git a/lang/zh_CN/entities.php b/lang/zh_CN/entities.php index 9daf816da04..bede7fb1c0d 100644 --- a/lang/zh_CN/entities.php +++ b/lang/zh_CN/entities.php @@ -173,7 +173,7 @@ '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_auto_sort_creation_hint' => '具备相关权限的用户可在 “列表与排序” 设置区域中创建自动排序选项规则。', 'books_sort_named' => '排序书籍「:bookName」', 'books_sort_name' => '按名称排序', 'books_sort_created' => '创建时间排序', @@ -331,9 +331,9 @@ // 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_contents' => '页面内容', + 'page_contents_none' => '未在页面内容中找到任何标题。', + 'page_contents_info' => '目录是根据页面中使用的所有标题格式生成的。', 'page_tags' => '页面标签', 'chapter_tags' => '章节标签', 'book_tags' => '书籍标签', diff --git a/lang/zh_CN/settings.php b/lang/zh_CN/settings.php index eef14b1687d..c1fae2dce47 100644 --- a/lang/zh_CN/settings.php +++ b/lang/zh_CN/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => '章节正序', 'sort_rule_op_chapters_last' => '章节倒序', 'sorting_page_limits' => '每页显示限制', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sorting_page_limits_desc' => '设置系统内各列表的单页显示数量。通常较少的数据量性能更佳,较多的数量则能减少用户的翻页操作。建议设置为 6 的倍数。', // Maintenance settings 'maint' => '维护', @@ -207,7 +207,7 @@ 'role_all' => '全部的', 'role_own' => '拥有的', 'role_controlled_by_asset' => '由其所在的资源来控制', - 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', + 'role_controlled_by_page_delete' => '受页面删除权限限制', 'role_save' => '保存角色', 'role_users' => '此角色的用户', 'role_users_none' => '目前没有用户被分配到这个角色', @@ -366,8 +366,9 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', - 'th' => 'ภาษาไทย', + 'th' => '泰语', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', diff --git a/lang/zh_TW/activities.php b/lang/zh_TW/activities.php index 791fa26b752..ac1524ec433 100644 --- a/lang/zh_TW/activities.php +++ b/lang/zh_TW/activities.php @@ -99,8 +99,8 @@ 'user_update_notification' => '使用者已成功更新。', 'user_delete' => '已刪除使用者', 'user_delete_notification' => '使用者移除成功', - 'user_mfa_reset' => 'reset MFA for user', - 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', + 'user_mfa_reset' => '重設使用者的多要素驗證', + 'user_mfa_reset_notification' => '重設多要素驗證方法', // API Tokens 'api_token_create' => '建立 API 權杖', diff --git a/lang/zh_TW/auth.php b/lang/zh_TW/auth.php index 47e8ed950b2..7ac341e16b0 100644 --- a/lang/zh_TW/auth.php +++ b/lang/zh_TW/auth.php @@ -8,7 +8,7 @@ 'failed' => '使用者名稱或密碼錯誤。', 'throttle' => '您的登入次數過多,請在 :seconds 秒後重試。', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => '多要素驗證嘗試次數過多。請於 :seconds 秒後再試。', // Login & Register 'sign_up' => '註冊', diff --git a/lang/zh_TW/entities.php b/lang/zh_TW/entities.php index ba53d885dd4..4c5cfed1de2 100644 --- a/lang/zh_TW/entities.php +++ b/lang/zh_TW/entities.php @@ -173,7 +173,7 @@ '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_auto_sort_creation_hint' => '具備相關權限的使用者可在「清單與排序」設定區域中建立自動排序選項規則。', 'books_sort_named' => '排序書本 :bookName', 'books_sort_name' => '按名稱排序', 'books_sort_created' => '按建立時間排序', @@ -331,9 +331,9 @@ // 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_contents' => '頁面內容', + 'page_contents_none' => '在頁面內容中未找到任何標題。', + 'page_contents_info' => '內容選單是根據頁面中使用的任何標題格式所產生的。', 'page_tags' => '頁面標籤', 'chapter_tags' => '章節標籤', 'book_tags' => '書本標籤', diff --git a/lang/zh_TW/errors.php b/lang/zh_TW/errors.php index e5c08ca141b..989ab0ad04f 100644 --- a/lang/zh_TW/errors.php +++ b/lang/zh_TW/errors.php @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => '給定使用的 API 權杖的密碼錯誤', 'api_user_no_api_permission' => '使用的 API 權杖擁有者無權呼叫 API', 'api_user_token_expired' => '使用的授權權杖已過期', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => '使用以 cookie 為基礎的驗證來呼叫 API 時,僅允許 GET 請求', // Settings & Maintenance 'maintenance_test_email_failure' => '寄送測試電子郵件時發生錯誤:', diff --git a/lang/zh_TW/settings.php b/lang/zh_TW/settings.php index 95108545019..e12f2caf5c6 100644 --- a/lang/zh_TW/settings.php +++ b/lang/zh_TW/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => '第一章', 'sort_rule_op_chapters_last' => '最後一章', 'sorting_page_limits' => '每頁顯示限制', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sorting_page_limits_desc' => '設定系統內各清單每頁顯示的項目數量。通常項目數量較少時效能較佳,而數量較多則可避免使用者需點擊多頁瀏覽。建議採用 6 的倍數。', // Maintenance settings 'maint' => '維護', @@ -208,7 +208,7 @@ 'role_all' => '全部', 'role_own' => '擁有', 'role_controlled_by_asset' => '依據隸屬的資源來決定', - 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', + 'role_controlled_by_page_delete' => '受頁面刪除權限控制', 'role_save' => '儲存角色', 'role_users' => '屬於此角色的使用者', 'role_users_none' => '目前沒有使用者被分配到此角色', @@ -265,9 +265,9 @@ '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?', + 'users_mfa_reset' => '重設多要素驗證方式', + 'users_mfa_reset_desc' => '此操作將重設並清除該使用者所有已設定的多要素驗證方法。若其任何角色要求使用多要素驗證,系統將在下次登入時提示其設定新的驗證方法。', + 'users_mfa_reset_confirm' => '您確定要重設此使用者的多要素驗證嗎?', // API Tokens 'user_api_token_create' => '建立 API 權杖', @@ -367,8 +367,9 @@ 'ru' => 'Русский', 'sk' => 'Slovensky', 'sl' => 'Slovenščina', + 'sr' => 'Српски', 'sv' => 'Svenska', - 'th' => 'ภาษาไทย', + 'th' => '泰語', 'tr' => 'Türkçe', 'uk' => 'Українська', 'uz' => 'O‘zbekcha', From ad283ef0ed91d5d70f392a78b62d973d491d3240 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 2 Jul 2026 09:41:17 +0100 Subject: [PATCH 192/204] Updated packages, translators and licensing pre v26.05.2 --- .github/translators.txt | 2 + composer.lock | 414 +++++++++++++------------ dev/licensing/php-library-licenses.txt | 2 +- 3 files changed, 211 insertions(+), 207 deletions(-) diff --git a/.github/translators.txt b/.github/translators.txt index 6aa2be470f3..a2f6a957066 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -546,3 +546,5 @@ lonestan :: Russian Paul Kernstock (kernstock) :: German brtbr :: German; German Informal Ricardo Covelo (covelo12) :: Portuguese +Bojan Maksimovic (PolarniMeda) :: Serbian (Cyrillic) +Dian Prawira (wiradian84) :: Indonesian diff --git a/composer.lock b/composer.lock index e0726839914..685d88f524d 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.384.5", + "version": "3.387.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "c7d34f2d60515bd0c307e462268f75877842da4a" + "reference": "2e1a16e9c87f2a069aa8a0a14a314dd148de21e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c7d34f2d60515bd0c307e462268f75877842da4a", - "reference": "c7d34f2d60515bd0c307e462268f75877842da4a", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2e1a16e9c87f2a069aa8a0a14a314dd148de21e1", + "reference": "2e1a16e9c87f2a069aa8a0a14a314dd148de21e1", "shasum": "" }, "require": { @@ -82,7 +82,7 @@ "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/promises": "^2.0", "guzzlehttp/psr7": "^2.4.5", - "mtdowling/jmespath.php": "^2.8.0", + "mtdowling/jmespath.php": "^2.9.1", "php": ">=8.1", "psr/http-message": "^1.0 || ^2.0", "symfony/filesystem": "^v5.4.45 || ^v6.4.3 || ^v7.1.0 || ^v8.0.0" @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.384.5" + "source": "https://github.com/aws/aws-sdk-php/tree/3.387.1" }, - "time": "2026-06-08T18:25:02+00:00" + "time": "2026-07-01T18:10:42+00:00" }, { "name": "bacon/bacon-qr-code", @@ -982,16 +982,16 @@ }, { "name": "firebase/php-jwt", - "version": "v7.0.5", + "version": "v7.1.0", "source": { "type": "git", "url": "https://github.com/googleapis/php-jwt.git", - "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380" + "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380", - "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/b374a5d1a4f1f67fadc2165cdb284645945e2fc0", + "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0", "shasum": "" }, "require": { @@ -1000,6 +1000,7 @@ "require-dev": { "guzzlehttp/guzzle": "^7.4", "phpfastcache/phpfastcache": "^9.2", + "phpseclib/phpseclib": "~3.0", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^9.5", "psr/cache": "^2.0||^3.0", @@ -1008,7 +1009,8 @@ }, "suggest": { "ext-sodium": "Support EdDSA (Ed25519) signatures", - "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present", + "phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures" }, "type": "library", "autoload": { @@ -1033,16 +1035,16 @@ } ], "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", - "homepage": "https://github.com/firebase/php-jwt", + "homepage": "https://github.com/googleapis/php-jwt", "keywords": [ "jwt", "php" ], "support": { "issues": "https://github.com/googleapis/php-jwt/issues", - "source": "https://github.com/googleapis/php-jwt/tree/v7.0.5" + "source": "https://github.com/googleapis/php-jwt/tree/v7.1.0" }, - "time": "2026-04-01T20:38:03+00:00" + "time": "2026-06-11T17:54:14+00:00" }, { "name": "fruitcake/php-cors", @@ -1179,26 +1181,26 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.11.1", + "version": "7.13.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "5af96f374e0ab4ebd747b8310888c99d3adb0a8c" + "reference": "55901a76dfd2006a0cc012b9e3c5b487f796478d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/5af96f374e0ab4ebd747b8310888c99d3adb0a8c", - "reference": "5af96f374e0ab4ebd747b8310888c99d3adb0a8c", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/55901a76dfd2006a0cc012b9e3c5b487f796478d", + "reference": "55901a76dfd2006a0cc012b9e3c5b487f796478d", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^2.5", - "guzzlehttp/psr7": "^2.11", + "guzzlehttp/psr7": "^2.12.3", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.5 || ^3.0", - "symfony/polyfill-php80": "^1.24" + "symfony/polyfill-php80": "^1.25" }, "provide": { "psr/http-client-implementation": "1.0" @@ -1207,7 +1209,7 @@ "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", "guzzle/client-integration-tests": "3.0.2", - "guzzlehttp/test-server": "^0.5", + "guzzlehttp/test-server": "^0.6", "php-http/message-factory": "^1.1", "phpunit/phpunit": "^8.5.52 || ^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" @@ -1287,7 +1289,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.11.1" + "source": "https://github.com/guzzle/guzzle/tree/7.13.1" }, "funding": [ { @@ -1303,7 +1305,7 @@ "type": "tidelift" } ], - "time": "2026-06-07T22:54:06+00:00" + "time": "2026-06-29T20:14:18+00:00" }, { "name": "guzzlehttp/promises", @@ -1391,16 +1393,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.11.0", + "version": "2.12.3", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f" + "reference": "7ec62dc3f44aa218487dbed81a9bf9bc647be55d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/bbb5e61349fa5cb822b3e87842b951088b76b81f", - "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7ec62dc3f44aa218487dbed81a9bf9bc647be55d", + "reference": "7ec62dc3f44aa218487dbed81a9bf9bc647be55d", "shasum": "" }, "require": { @@ -1409,7 +1411,7 @@ "psr/http-message": "^1.1 || ^2.0", "ralouphie/getallheaders": "^3.0", "symfony/deprecation-contracts": "^2.5 || ^3.0", - "symfony/polyfill-php80": "^1.24" + "symfony/polyfill-php80": "^1.25" }, "provide": { "psr/http-factory-implementation": "1.0", @@ -1490,7 +1492,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.11.0" + "source": "https://github.com/guzzle/psr7/tree/2.12.3" }, "funding": [ { @@ -1506,25 +1508,25 @@ "type": "tidelift" } ], - "time": "2026-06-02T12:30:48+00:00" + "time": "2026-06-23T15:21:08+00:00" }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.6", + "version": "v1.0.8", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "eef7f87bab6f204eba3c39224d8075c70c637946" + "reference": "9c19128923b05a5d7355e5d2318d7808b7e33bbd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/eef7f87bab6f204eba3c39224d8075c70c637946", - "reference": "eef7f87bab6f204eba3c39224d8075c70c637946", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/9c19128923b05a5d7355e5d2318d7808b7e33bbd", + "reference": "9c19128923b05a5d7355e5d2318d7808b7e33bbd", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "symfony/polyfill-php80": "^1.24" + "symfony/polyfill-php80": "^1.25" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", @@ -1576,7 +1578,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.6" + "source": "https://github.com/guzzle/uri-template/tree/v1.0.8" }, "funding": [ { @@ -1592,7 +1594,7 @@ "type": "tidelift" } ], - "time": "2026-05-23T22:00:21+00:00" + "time": "2026-06-23T13:02:23+00:00" }, { "name": "intervention/gif", @@ -1807,16 +1809,16 @@ }, { "name": "laravel/framework", - "version": "v12.61.1", + "version": "v12.62.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "e8472ca9774452fe50841d9bdced060679f4d58d" + "reference": "f7e61eb1e0e06a38996802b769bce9127aec227c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/e8472ca9774452fe50841d9bdced060679f4d58d", - "reference": "e8472ca9774452fe50841d9bdced060679f4d58d", + "url": "https://api.github.com/repos/laravel/framework/zipball/f7e61eb1e0e06a38996802b769bce9127aec227c", + "reference": "f7e61eb1e0e06a38996802b769bce9127aec227c", "shasum": "" }, "require": { @@ -2025,20 +2027,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-06-04T14:22:52+00:00" + "time": "2026-06-09T13:50:13+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.18", + "version": "v0.3.21", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "a19af51bb144bf87f08397921fa619f85c7d4e72" + "reference": "7753c65c281c2550c7c183f14e18062073b7d821" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/a19af51bb144bf87f08397921fa619f85c7d4e72", - "reference": "a19af51bb144bf87f08397921fa619f85c7d4e72", + "url": "https://api.github.com/repos/laravel/prompts/zipball/7753c65c281c2550c7c183f14e18062073b7d821", + "reference": "7753c65c281c2550c7c183f14e18062073b7d821", "shasum": "" }, "require": { @@ -2082,9 +2084,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.18" + "source": "https://github.com/laravel/prompts/tree/v0.3.21" }, - "time": "2026-05-19T00:47:18+00:00" + "time": "2026-06-26T00:11:25+00:00" }, { "name": "laravel/serializable-closure", @@ -2149,16 +2151,16 @@ }, { "name": "laravel/socialite", - "version": "v5.27.0", + "version": "v5.28.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "40e0757a75637c7b2dff05d3286b0d8fc25e5c0e" + "reference": "4c131ff4b24d8881a9c8fe4eecb5ffeff9803f26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/40e0757a75637c7b2dff05d3286b0d8fc25e5c0e", - "reference": "40e0757a75637c7b2dff05d3286b0d8fc25e5c0e", + "url": "https://api.github.com/repos/laravel/socialite/zipball/4c131ff4b24d8881a9c8fe4eecb5ffeff9803f26", + "reference": "4c131ff4b24d8881a9c8fe4eecb5ffeff9803f26", "shasum": "" }, "require": { @@ -2217,7 +2219,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2026-04-24T14:05:47+00:00" + "time": "2026-06-12T03:24:05+00:00" }, { "name": "laravel/tinker", @@ -2476,16 +2478,16 @@ }, { "name": "league/flysystem", - "version": "3.34.0", + "version": "3.35.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "2daaac3b0d4c83ea7ed5d8586e786f5d00f3540e" + "reference": "f23af6c5aafd958a7593029a271d77baf5ed793c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2daaac3b0d4c83ea7ed5d8586e786f5d00f3540e", - "reference": "2daaac3b0d4c83ea7ed5d8586e786f5d00f3540e", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f23af6c5aafd958a7593029a271d77baf5ed793c", + "reference": "f23af6c5aafd958a7593029a271d77baf5ed793c", "shasum": "" }, "require": { @@ -2553,22 +2555,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.34.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.35.1" }, - "time": "2026-05-14T10:28:08+00:00" + "time": "2026-06-25T06:52:23+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.34.0", + "version": "3.35.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "0c62fdac907791d8649ad3c61cb7a77628344fb8" + "reference": "3f93d3f4bd5c12a5dfb34a7267c40b1bc4587b94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/0c62fdac907791d8649ad3c61cb7a77628344fb8", - "reference": "0c62fdac907791d8649ad3c61cb7a77628344fb8", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/3f93d3f4bd5c12a5dfb34a7267c40b1bc4587b94", + "reference": "3f93d3f4bd5c12a5dfb34a7267c40b1bc4587b94", "shasum": "" }, "require": { @@ -2608,9 +2610,9 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.34.0" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.35.1" }, - "time": "2026-05-04T08:24:00+00:00" + "time": "2026-06-25T06:51:08+00:00" }, { "name": "league/flysystem-local", @@ -3131,16 +3133,16 @@ }, { "name": "masterminds/html5", - "version": "2.10.0", + "version": "2.10.1", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "fcf91eb64359852f00d921887b219479b4f21251" + "reference": "fd5018f6815fff903946d0564977b44ce8010e29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", - "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fd5018f6815fff903946d0564977b44ce8010e29", + "reference": "fd5018f6815fff903946d0564977b44ce8010e29", "shasum": "" }, "require": { @@ -3148,7 +3150,7 @@ "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9 || ^10" }, "type": "library", "extra": { @@ -3192,9 +3194,9 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + "source": "https://github.com/Masterminds/html5-php/tree/2.10.1" }, - "time": "2025-07-25T09:04:22+00:00" + "time": "2026-06-23T18:43:15+00:00" }, { "name": "monolog/monolog", @@ -3301,16 +3303,16 @@ }, { "name": "mtdowling/jmespath.php", - "version": "2.8.0", + "version": "2.9.1", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" + "reference": "9c208ba27ae7d90853c288b3795d6702eb251d34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", - "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9c208ba27ae7d90853c288b3795d6702eb251d34", + "reference": "9c208ba27ae7d90853c288b3795d6702eb251d34", "shasum": "" }, "require": { @@ -3319,7 +3321,7 @@ }, "require-dev": { "composer/xdebug-handler": "^3.0.3", - "phpunit/phpunit": "^8.5.33" + "phpunit/phpunit": "^8.5.52" }, "bin": [ "bin/jp.php" @@ -3327,7 +3329,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "2.9-dev" } }, "autoload": { @@ -3361,22 +3363,22 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" + "source": "https://github.com/jmespath/jmespath.php/tree/2.9.1" }, - "time": "2024-09-04T18:46:31+00:00" + "time": "2026-06-11T10:43:56+00:00" }, { "name": "nesbot/carbon", - "version": "3.11.4", + "version": "3.13.0", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "e890471a3494740f7d9326d72ce6a8c559ffee60" + "reference": "40f6618f052df16b545f626fbf9a878e6497d16a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/e890471a3494740f7d9326d72ce6a8c559ffee60", - "reference": "e890471a3494740f7d9326d72ce6a8c559ffee60", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/40f6618f052df16b545f626fbf9a878e6497d16a", + "reference": "40f6618f052df16b545f626fbf9a878e6497d16a", "shasum": "" }, "require": { @@ -3468,7 +3470,7 @@ "type": "tidelift" } ], - "time": "2026-04-07T09:57:54+00:00" + "time": "2026-06-18T13:49:15+00:00" }, { "name": "nette/schema", @@ -4033,16 +4035,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.52", + "version": "3.0.55", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "2adaefc83df2ec548558307690f376dd7d4f4fce" + "reference": "db9744e6d47e742b1f974e965ad49bdd041105af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2adaefc83df2ec548558307690f376dd7d4f4fce", - "reference": "2adaefc83df2ec548558307690f376dd7d4f4fce", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db9744e6d47e742b1f974e965ad49bdd041105af", + "reference": "db9744e6d47e742b1f974e965ad49bdd041105af", "shasum": "" }, "require": { @@ -4123,7 +4125,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.52" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.55" }, "funding": [ { @@ -4139,7 +4141,7 @@ "type": "tidelift" } ], - "time": "2026-04-27T07:02:15+00:00" + "time": "2026-06-14T23:24:10+00:00" }, { "name": "pragmarx/google2fa", @@ -4195,16 +4197,16 @@ }, { "name": "predis/predis", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "8cc4319c06924c8ff0c5c7eec4243a19e3be32f1" + "reference": "5c996db191ee2d9bafe651f454b1fca16754271b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/8cc4319c06924c8ff0c5c7eec4243a19e3be32f1", - "reference": "8cc4319c06924c8ff0c5c7eec4243a19e3be32f1", + "url": "https://api.github.com/repos/predis/predis/zipball/5c996db191ee2d9bafe651f454b1fca16754271b", + "reference": "5c996db191ee2d9bafe651f454b1fca16754271b", "shasum": "" }, "require": { @@ -4246,7 +4248,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v3.5.0" + "source": "https://github.com/predis/predis/tree/v3.5.1" }, "funding": [ { @@ -4254,7 +4256,7 @@ "type": "github" } ], - "time": "2026-06-02T19:25:56+00:00" + "time": "2026-06-11T16:56:53+00:00" }, { "name": "psr/clock", @@ -4670,16 +4672,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.23", + "version": "v0.12.24", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "4dcc0f08047d52bbde475eda481146fd8e27e1a4" + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4dcc0f08047d52bbde475eda481146fd8e27e1a4", - "reference": "4dcc0f08047d52bbde475eda481146fd8e27e1a4", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", "shasum": "" }, "require": { @@ -4743,9 +4745,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.23" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.24" }, - "time": "2026-05-23T13:41:31+00:00" + "time": "2026-06-29T15:41:09+00:00" }, { "name": "ralouphie/getallheaders", @@ -4869,20 +4871,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.2", + "version": "4.9.3", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "8429c78ca35a09f27565311b98101e2826affde0" + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", - "reference": "8429c78ca35a09f27565311b98101e2826affde0", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/1df15849d00943a67d677dc9cfd80795f038c9f8", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8", "shasum": "" }, "require": { - "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "brick/math": ">=0.8.16 <=0.18", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -4941,9 +4943,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.2" + "source": "https://github.com/ramsey/uuid/tree/4.9.3" }, - "time": "2025-12-14T04:43:48+00:00" + "time": "2026-06-18T03:57:49+00:00" }, { "name": "robrichards/xmlseclibs", @@ -4989,16 +4991,16 @@ }, { "name": "sabberworm/php-css-parser", - "version": "v9.3.0", + "version": "v9.4.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949" + "reference": "fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", - "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f", + "reference": "fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f", "shasum": "" }, "require": { @@ -5009,15 +5011,15 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.32 || 2.1.32", - "phpstan/phpstan-phpunit": "1.4.2 || 2.0.8", - "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7", + "phpstan/phpstan": "1.12.33 || 2.2.2", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.16", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.11", "phpunit/phpunit": "8.5.52", "rawr/phpunit-data-provider": "3.3.1", - "rector/rector": "1.2.10 || 2.2.8", - "rector/type-perfect": "1.0.0 || 2.1.0", + "rector/rector": "1.2.10 || 2.4.6", + "rector/type-perfect": "1.0.0 || 2.1.3", "squizlabs/php_codesniffer": "4.0.1", - "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1" + "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.3" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" @@ -5025,7 +5027,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.4.x-dev" + "dev-main": "9.5.x-dev" } }, "autoload": { @@ -5063,9 +5065,9 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.4.0" }, - "time": "2026-03-03T17:31:43+00:00" + "time": "2026-06-18T15:10:53+00:00" }, { "name": "socialiteproviders/discord", @@ -5515,16 +5517,16 @@ }, { "name": "symfony/console", - "version": "v7.4.13", + "version": "v7.4.14", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217" + "reference": "92f58bc4bf97a92ed1b9f367f0cd44f20bde0e87" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/85095d2573eaefaf35e40b9513a9bf09f72cd217", - "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217", + "url": "https://api.github.com/repos/symfony/console/zipball/92f58bc4bf97a92ed1b9f367f0cd44f20bde0e87", + "reference": "92f58bc4bf97a92ed1b9f367f0cd44f20bde0e87", "shasum": "" }, "require": { @@ -5589,7 +5591,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.13" + "source": "https://github.com/symfony/console/tree/v7.4.14" }, "funding": [ { @@ -5609,7 +5611,7 @@ "type": "tidelift" } ], - "time": "2026-05-24T08:56:14+00:00" + "time": "2026-06-16T11:50:14+00:00" }, { "name": "symfony/css-selector", @@ -5682,16 +5684,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { @@ -5729,7 +5731,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -5749,20 +5751,20 @@ "type": "tidelift" } ], - "time": "2026-04-13T15:52:40+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/error-handler", - "version": "v7.4.8", + "version": "v7.4.14", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa" + "reference": "4e1a093b481f323e6e326451f9760c3868430673" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa", - "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/4e1a093b481f323e6e326451f9760c3868430673", + "reference": "4e1a093b481f323e6e326451f9760c3868430673", "shasum": "" }, "require": { @@ -5811,7 +5813,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.4.8" + "source": "https://github.com/symfony/error-handler/tree/v7.4.14" }, "funding": [ { @@ -5831,20 +5833,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-06-05T06:22:21+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.9", + "version": "v7.4.14", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101" + "reference": "51fe3d170227be8d1772214b82ae506e15ed78ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e4a2e29753c7801f7a8340e066cfa788f3bc8101", - "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/51fe3d170227be8d1772214b82ae506e15ed78ff", + "reference": "51fe3d170227be8d1772214b82ae506e15ed78ff", "shasum": "" }, "require": { @@ -5896,7 +5898,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.9" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.14" }, "funding": [ { @@ -5916,20 +5918,20 @@ "type": "tidelift" } ], - "time": "2026-04-18T13:18:21+00:00" + "time": "2026-06-06T11:10:32+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", - "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", "shasum": "" }, "require": { @@ -5976,7 +5978,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" }, "funding": [ { @@ -5996,7 +5998,7 @@ "type": "tidelift" } ], - "time": "2026-01-05T13:30:16+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/filesystem", @@ -6070,16 +6072,16 @@ }, { "name": "symfony/finder", - "version": "v7.4.8", + "version": "v7.4.14", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "e0be088d22278583a82da281886e8c3592fbf149" + "reference": "13b38720174286f55d1761152b575a8d1436fc25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/e0be088d22278583a82da281886e8c3592fbf149", - "reference": "e0be088d22278583a82da281886e8c3592fbf149", + "url": "https://api.github.com/repos/symfony/finder/zipball/13b38720174286f55d1761152b575a8d1436fc25", + "reference": "13b38720174286f55d1761152b575a8d1436fc25", "shasum": "" }, "require": { @@ -6114,7 +6116,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.8" + "source": "https://github.com/symfony/finder/tree/v7.4.14" }, "funding": [ { @@ -6134,20 +6136,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-06-27T08:31:18+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.13", + "version": "v7.4.14", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "bc354f47c62301e990b7874fa662326368508e2c" + "reference": "06db5ae1552177bf8572f8908839f12e3c06aed3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/bc354f47c62301e990b7874fa662326368508e2c", - "reference": "bc354f47c62301e990b7874fa662326368508e2c", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/06db5ae1552177bf8572f8908839f12e3c06aed3", + "reference": "06db5ae1552177bf8572f8908839f12e3c06aed3", "shasum": "" }, "require": { @@ -6196,7 +6198,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.13" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.14" }, "funding": [ { @@ -6216,20 +6218,20 @@ "type": "tidelift" } ], - "time": "2026-05-24T11:20:33+00:00" + "time": "2026-06-11T07:31:44+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.13", + "version": "v7.4.14", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "9df847980c436451f4f51d1284491bb4356dd989" + "reference": "e99af79b1e776646eda0e1c23b7b45c184ff99be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9df847980c436451f4f51d1284491bb4356dd989", - "reference": "9df847980c436451f4f51d1284491bb4356dd989", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/e99af79b1e776646eda0e1c23b7b45c184ff99be", + "reference": "e99af79b1e776646eda0e1c23b7b45c184ff99be", "shasum": "" }, "require": { @@ -6315,7 +6317,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.13" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.14" }, "funding": [ { @@ -6335,20 +6337,20 @@ "type": "tidelift" } ], - "time": "2026-05-27T08:31:43+00:00" + "time": "2026-06-27T09:14:35+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.12", + "version": "v7.4.14", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "5cefb712a25f320579615ba9e1942abaeade7dff" + "reference": "f88ce03ae73e3edb5c176ce1f337709996e88495" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/5cefb712a25f320579615ba9e1942abaeade7dff", - "reference": "5cefb712a25f320579615ba9e1942abaeade7dff", + "url": "https://api.github.com/repos/symfony/mailer/zipball/f88ce03ae73e3edb5c176ce1f337709996e88495", + "reference": "f88ce03ae73e3edb5c176ce1f337709996e88495", "shasum": "" }, "require": { @@ -6399,7 +6401,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.12" + "source": "https://github.com/symfony/mailer/tree/v7.4.14" }, "funding": [ { @@ -6419,7 +6421,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-06-13T08:51:35+00:00" }, { "name": "symfony/mime", @@ -7491,16 +7493,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", "shasum": "" }, "require": { @@ -7554,7 +7556,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" }, "funding": [ { @@ -7574,7 +7576,7 @@ "type": "tidelift" } ], - "time": "2026-03-28T09:44:51+00:00" + "time": "2026-06-16T09:55:08+00:00" }, { "name": "symfony/string", @@ -7669,16 +7671,16 @@ }, { "name": "symfony/translation", - "version": "v7.4.10", + "version": "v7.4.14", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "ada7578c30dd5feaa8259cff3e885069ea81ddde" + "reference": "a1af4dacb24eb7ef4f1ca71b94da8ddbce572281" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/ada7578c30dd5feaa8259cff3e885069ea81ddde", - "reference": "ada7578c30dd5feaa8259cff3e885069ea81ddde", + "url": "https://api.github.com/repos/symfony/translation/zipball/a1af4dacb24eb7ef4f1ca71b94da8ddbce572281", + "reference": "a1af4dacb24eb7ef4f1ca71b94da8ddbce572281", "shasum": "" }, "require": { @@ -7745,7 +7747,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.10" + "source": "https://github.com/symfony/translation/tree/v7.4.14" }, "funding": [ { @@ -7765,20 +7767,20 @@ "type": "tidelift" } ], - "time": "2026-05-06T11:19:24+00:00" + "time": "2026-06-06T09:33:19+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d" + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d", - "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", "shasum": "" }, "require": { @@ -7827,7 +7829,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" }, "funding": [ { @@ -7847,7 +7849,7 @@ "type": "tidelift" } ], - "time": "2026-01-05T13:30:16+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/uid", @@ -7929,16 +7931,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.4.8", + "version": "v7.4.14", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd" + "reference": "9a3a56a4a1e65a5cb4f8d13801fe8ab0a170e358" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9510c3966f749a1d1ff0059e1eabef6cc621e7fd", - "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9a3a56a4a1e65a5cb4f8d13801fe8ab0a170e358", + "reference": "9a3a56a4a1e65a5cb4f8d13801fe8ab0a170e358", "shasum": "" }, "require": { @@ -7992,7 +7994,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.8" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.14" }, "funding": [ { @@ -8012,7 +8014,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T13:44:50+00:00" + "time": "2026-06-08T20:24:16+00:00" }, { "name": "thecodingmachine/safe", @@ -9183,11 +9185,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.2.2", + "version": "2.2.3", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e5cc34d491a90e79c216d824f60fe21fd4d93bd6", - "reference": "e5cc34d491a90e79c216d824f60fe21fd4d93bd6", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/4048833dd47b377287818841877fb3087289509c", + "reference": "4048833dd47b377287818841877fb3087289509c", "shasum": "" }, "require": { @@ -9243,7 +9245,7 @@ "type": "github" } ], - "time": "2026-06-05T09:00:01+00:00" + "time": "2026-06-30T21:15:26+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/dev/licensing/php-library-licenses.txt b/dev/licensing/php-library-licenses.txt index 2259c44c301..0c30fd5bb14 100644 --- a/dev/licensing/php-library-licenses.txt +++ b/dev/licensing/php-library-licenses.txt @@ -110,7 +110,7 @@ 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/firebase/php-jwt +Link: https://github.com/googleapis/php-jwt ----------- fruitcake/php-cors License: MIT From 6107161275b3594e17847d9f5362b9786fa8bf09 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 2 Jul 2026 10:21:01 +0100 Subject: [PATCH 193/204] URLs: Fixed issue in comparisons when elements missing --- app/Util/UrlComparison.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Util/UrlComparison.php b/app/Util/UrlComparison.php index 66984918d29..9fda2d6142b 100644 --- a/app/Util/UrlComparison.php +++ b/app/Util/UrlComparison.php @@ -18,9 +18,9 @@ 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']; + return ($aParts['host'] ?? '') === ($bParts['host'] ?? '') + && ($aParts['scheme'] ?? '') === ($bParts['scheme'] ?? '') + && ($aParts['port'] ?? '') === ($bParts['port'] ?? ''); } /** From 4e406c41c4c8060a5795e74c66fb96362e54f400 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 23 Jul 2026 20:57:48 +0100 Subject: [PATCH 194/204] Attachments: Aligned update form permission checks with other actions Thanks to Ashutosh Jena(MAVERICK-VF142) for reporting. Not considered a significant security issue since it already required page update permissions, which would generally be considered higher privileged than the added page view. --- .../Controllers/AttachmentController.php | 3 ++- tests/Uploads/AttachmentTest.php | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/Uploads/Controllers/AttachmentController.php b/app/Uploads/Controllers/AttachmentController.php index aa9e0e29195..5e128bc3466 100644 --- a/app/Uploads/Controllers/AttachmentController.php +++ b/app/Uploads/Controllers/AttachmentController.php @@ -93,8 +93,9 @@ public function getUpdateForm(string $attachmentId) /** @var Attachment $attachment */ $attachment = Attachment::query()->findOrFail($attachmentId); + $this->checkOwnablePermission(Permission::PageView, $attachment->page); $this->checkOwnablePermission(Permission::PageUpdate, $attachment->page); - $this->checkOwnablePermission(Permission::AttachmentCreate, $attachment); + $this->checkOwnablePermission(Permission::AttachmentUpdate, $attachment); return view('attachments.manager-edit-form', [ 'attachment' => $attachment, diff --git a/tests/Uploads/AttachmentTest.php b/tests/Uploads/AttachmentTest.php index 3f8a2aaf14f..30e45fff6fc 100644 --- a/tests/Uploads/AttachmentTest.php +++ b/tests/Uploads/AttachmentTest.php @@ -274,6 +274,26 @@ public function test_attachment_access_without_permission_shows_404() $this->files->deleteAllAttachmentFiles(); } + public function test_attachment_edit_form_access_requires_view_permission() + { + $page = $this->entities->page(); + /** @var Attachment $attachment */ + $attachment = Attachment::factory()->create(['uploaded_to' => $page->id]); + $editor = $this->users->editor(); + + $this->permissions->disableEntityInheritedPermissions($page); + $this->permissions->grantUserRolePermissions($editor, [Permission::AttachmentUpdateAll]); + $this->permissions->setEntityPermissionsForRole($page, ['update'], $editor->roles()->first()); + + $resp = $this->actingAs($editor)->get("/attachments/edit/{$attachment->id}"); + $this->assertPermissionError($resp); + + $this->permissions->setEntityPermissionsForRole($page, ['view', 'update'], $editor->roles()->first()); + $resp = $this->actingAs($editor)->get("/attachments/edit/{$attachment->id}"); + $resp->assertOk(); + $resp->assertSee($attachment->name); + } + public function test_data_and_js_links_cannot_be_attached_to_a_page() { $page = $this->entities->page(); From 0ada5d2dc9a0cf4a637d48cd659f7d0c20e50ad5 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 24 Jul 2026 14:33:47 +0100 Subject: [PATCH 195/204] Login: Added extra timing defenses for failed login attempts - Adds a dummy hash attempt to balance the time of unknown user login attempt with known user login attempt to help prevent timing being used to indicate existing accounts. - Adds some random variance to failed login attempts to help prevent timing based information discovery. Thanks to Tanner Marks for their responsible disclosure of this. --- app/Access/LoginService.php | 15 +++++++++++++- tests/Auth/AuthTest.php | 39 +++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/app/Access/LoginService.php b/app/Access/LoginService.php index 769290c1648..6547926c559 100644 --- a/app/Access/LoginService.php +++ b/app/Access/LoginService.php @@ -13,6 +13,7 @@ use BookStack\Theming\ThemeEvents; use BookStack\Users\Models\User; use Exception; +use Illuminate\Support\Facades\Hash; class LoginService { @@ -171,10 +172,22 @@ public function attempt(array $credentials, string $method, bool $remember = fal } catch (LoginAttemptInvalidUserException $e) { // Catch and return false for non-login accounts // so it looks like a normal invalid login. - return false; + $result = false; } } + // Perform a dummy hash check to balance out the time of a login with an existing known user + // with that of a user not in the system (which we don't perform a hash check for in the above). + if (!$result && auth()->getLastAttempted() === null) { + Hash::check($credentials['password'], '$2y$04$A.H9icXH4/lxLd9DHuaYqO/GVBd0OKetxyY0txmNfTAlPLVnTBx3y'); + } + + // Add some noise to request times on failed login attempts + if (!$result) { + $sleepMs = random_int(0, 250); + usleep($sleepMs * 1000); + } + return $result; } diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index b42f7cb40d6..4cd793fdff3 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -3,6 +3,7 @@ namespace Tests\Auth; use BookStack\Access\Mfa\MfaSession; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Hash; use Illuminate\Testing\TestResponse; use Tests\TestCase; @@ -172,6 +173,44 @@ public function test_login_specifically_disabled_for_guest_account() $resp->assertSee('These credentials do not match our records.'); } + public function test_failed_login_attempt_has_noise_added_and_have_similar_times_between_known_and_unknown_users() + { + $this->markTestSkipped('Time consuming test'); + + $user = $this->users->editor(); + $user->password = bcrypt('password'); + $user->save(); + // Warmup + $this->post('/login', ['email' => $user->email, 'password' => 'passwordtesting']); + + // For known user attempts + $durations = []; + for ($i = 0; $i < 25; $i++) { + $knownStart = microtime(true); + $this->post('/login', ['email' => $user->email, 'password' => 'passwordtesting']); + $durations[] = (microtime(true) - $knownStart) * 1000; + Cache::clear(); // Clear the cache to avoid hitting rate limits + } + $range = max($durations) - min($durations); + $this->assertGreaterThan(125, $range); + $knownAvg = array_sum($durations) / count($durations); + + // For unknown user attempts + $durations = []; + for ($i = 0; $i < 25; $i++) { + $unknownStart = microtime(true); + $this->post('/login', ['email' => 'unknown@example.com', 'password' => 'passwordtesting']); + $durations[] = (microtime(true) - $unknownStart) * 1000; + Cache::clear(); // Clear the cache to avoid hitting rate limits + } + $range = max($durations) - min($durations); + $this->assertGreaterThan(125, $range); + $unknownAvg = array_sum($durations) / count($durations); + + $knownDiff = abs($knownAvg - $unknownAvg); + $this->assertLessThan(25, $knownDiff); + } + /** * Perform a login. */ From cb195999ebbaae17ac98733712d7acae3a45e42c Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 27 Jul 2026 12:12:07 +0100 Subject: [PATCH 196/204] Content Filter: Updated srcset and URI handling - Added more complex srcset parsing and URI handling via custom built HTMLPurifier filter, which I've also provided upstream. - Added a custom URI filter to force require URI schemes. --- .../HtmlPurifier/ConfiguredHtmlPurifier.php | 11 +- .../HtmlPurifier/Filters/UriEnsureScheme.php | 39 ++++ app/Util/HtmlPurifier/SrcsetAttrDef.php | 201 +++++++++++++++++- tests/Entity/PageContentFilteringTest.php | 29 +++ 4 files changed, 270 insertions(+), 10 deletions(-) create mode 100644 app/Util/HtmlPurifier/Filters/UriEnsureScheme.php diff --git a/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php b/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php index 221db6ffc39..c4dd91778c6 100644 --- a/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php +++ b/app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php @@ -3,6 +3,7 @@ namespace BookStack\Util\HtmlPurifier; use BookStack\App\AppVersion; +use BookStack\Util\HtmlPurifier\Filters\UriEnsureScheme; use BookStack\Util\HtmlPurifier\Filters\UriLimitFileProtocolToAnchors; use BookStack\Util\UrlFilter; use HTMLPurifier; @@ -84,14 +85,18 @@ protected function setConfig(HTMLPurifier_Config $config, string $cachePath): vo $config->set('Attr.EnableID', true); $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; } + $defaultScheme = str_starts_with(url('/'), 'http:') ? 'http' : 'https'; + $config->set('URI.SafeIframeRegexp', '%^(http://|https://|//)%'); $config->set('URI.AllowedSchemes', $allowedSchemesSetting); + $config->set('URI.MakeAbsolute', true); + $config->set('URI.DefaultScheme', $defaultScheme); + $config->set('URI.Base', url('/')); // $config->set('Cache.DefinitionImpl', null); // Disable cache during testing } @@ -155,7 +160,8 @@ protected function configureHtmlDefinition(HTMLPurifier_HTMLDefinition $definiti // Allow mention-ids on links $definition->addAttribute('a', 'data-mention-user-id', 'Number'); - // Set up custom handler for srcset to limit accepted types + // Set up custom handler for srcset + // To remove once added upstream: https://github.com/xemlock/htmlpurifier-html5/pull/91 $definition->addAttribute('img', 'srcset', new SrcsetAttrDef()); $definition->addAttribute('source', 'srcset', new SrcsetAttrDef()); } @@ -163,6 +169,7 @@ protected function configureHtmlDefinition(HTMLPurifier_HTMLDefinition $definiti protected function configureUriDefinition(HTMLPurifier_URIDefinition $definition): void { $definition->registerFilter(new UriLimitFileProtocolToAnchors()); + $definition->registerFilter(new UriEnsureScheme()); } public function purify(string $html): string diff --git a/app/Util/HtmlPurifier/Filters/UriEnsureScheme.php b/app/Util/HtmlPurifier/Filters/UriEnsureScheme.php new file mode 100644 index 00000000000..6b4651b69c3 --- /dev/null +++ b/app/Util/HtmlPurifier/Filters/UriEnsureScheme.php @@ -0,0 +1,39 @@ +getDefinition('URI'); + $defaultScheme = $def->defaultScheme ?? ''; + + if (empty($uri->scheme) && $defaultScheme) { + $uri->scheme = $defaultScheme; + } + + return true; + } +} diff --git a/app/Util/HtmlPurifier/SrcsetAttrDef.php b/app/Util/HtmlPurifier/SrcsetAttrDef.php index 3b8417a55e2..2f0ffffab30 100644 --- a/app/Util/HtmlPurifier/SrcsetAttrDef.php +++ b/app/Util/HtmlPurifier/SrcsetAttrDef.php @@ -3,24 +3,209 @@ namespace BookStack\Util\HtmlPurifier; use HTMLPurifier_AttrDef; +use HTMLPurifier_AttrDef_URI; /** - * Custom attribute definition to filter out potentially dangerous - * values from the srcset attribute. + * A custom attribute definition for handling Srcset attributes. + * Has been provided upstream via: + * https://github.com/xemlock/htmlpurifier-html5/pull/91 + * but awaiting review/merge. */ class SrcsetAttrDef extends HTMLPurifier_AttrDef { public function validate($string, $config, $context) { - $lower = strtolower($string); - $nonAllowed = ['javascript:', 'vbscript:', 'data:', 'file:']; + $sources = $this->parseImageSources($string); + if (empty($sources)) { + return false; + } + + $uriFilter = new HTMLPurifier_AttrDef_URI(true); + + $filtered = array(); + foreach ($sources as $source) { + $uri = $source['uri']; + $descriptor = $source['descriptor']; + $validatedUri = $uriFilter->validate($uri, $config, $context); + if (is_string($validatedUri)) { + if ($descriptor) { + $filtered[] = $validatedUri . ' ' . $source['descriptor']; + } else { + $filtered[] = $validatedUri; + } + } + } + + if (empty($filtered)) { + return false; + } + + return implode(', ', $filtered); + } + + /** + * Parse the image source from srcset attribute text. + * Returns false if it's found to be invalid, otherwise + * returns an array of uri and descriptor combinations. + * + * This aims to follow the WHATWG parsing spec as per: + * https://html.spec.whatwg.org/multipage/images.html#parsing-a-srcset-attribute + * + * @param string $string + * @return array{uri: string, descriptor: string}[]|false + */ + private function parseImageSources($string) + { + $imageSources = array(); + $asciiWhitespace = " \n\r\t\f"; + $asciiWhiteSpaceComma = $asciiWhitespace . ','; + $input = trim($string, $asciiWhiteSpaceComma); + + if ($input === "") { + return false; + } + + $position = 0; + while ($position < strlen($input)) { + $position += strspn($input, $asciiWhitespace, $position); + $urlEnd = $position + strcspn($input, $asciiWhitespace, $position); + $url = substr($input, $position, $urlEnd - $position); + $position = $urlEnd; + $descriptors = array(); + + if (strpos($url, ',') === strlen($url) - 1) { + $url = rtrim($url, ','); + } else { + $position += strspn($input, $asciiWhitespace, $position); + $currentDescriptor = ''; + $state = 'in_descriptor'; + while (true) { + if ($position < strlen($input)) { + $c = $input[$position]; + } else { + $c = null; + } + + if ($state === 'in_descriptor') { + if ($c !== null && strpos($asciiWhitespace, $c) !== false) { + if ($currentDescriptor !== '') { + $descriptors[] = $currentDescriptor; + } + $state = 'after_descriptor'; + } else if ($c === ',') { + $position++; + if ($currentDescriptor !== '') { + $descriptors[] = $currentDescriptor; + } + break; + } else if ($c === '(') { + $currentDescriptor .= $c; + $state = 'in_parens'; + } else if ($c === null) { + if ($currentDescriptor !== '') { + $descriptors[] = $currentDescriptor; + } + break; + } else { + $currentDescriptor .= $c; + } + } else if ($state === 'in_parens') { + if ($c === ')') { + $currentDescriptor .= $c; + $state = 'in_descriptor'; + } else if ($c === null) { + $descriptors[] = $currentDescriptor; + break; + } else { + $currentDescriptor .= $c; + } + } else if ($state === 'after_descriptor') { + if ($c !== null && strpos($asciiWhitespace, $c) !== false) { + // Stay in this state + } else if ($c === null) { + break; + } else { + $state = 'in_descriptor'; + $position--; + } + } + + $position++; + } + } + + $descriptor = $this->formatDescriptor($descriptors); - foreach ($nonAllowed as $nonAllowedString) { - if (str_contains($lower, $nonAllowedString)) { - return false; + if ($url && $descriptor !== false) { + $imageSources[] = array( + 'uri' => $url, + 'descriptor' => $descriptor, + ); } } - return $string; + return $imageSources; + } + + /** + * Parse and format a single descriptor from an array of potential + * descriptor strings. Returns empty if valid but no descriptor. + * Returns false if invalid. + * @param string[] $descriptors + * @return false|string + */ + private function formatDescriptor(array $descriptors) + { + $error = false; + $width = ''; + $density = ''; + $futureCompatH = ''; + + foreach ($descriptors as $descriptor) { + $descriptor = trim($descriptor); + if ($descriptor === '') { + continue; + } + + $unit = $descriptor[strlen($descriptor) - 1]; + $number = trim(substr($descriptor, 0, -1)); + + if ($unit === 'w' && filter_var($number, FILTER_VALIDATE_INT) && intval($number) >= 0) { + if (!empty($width) || !empty($density) || intval($number) === 0) { + $error = true; + } + $width = $number; + } else if ($unit === 'x' && filter_var($number, FILTER_VALIDATE_FLOAT)) { + if (!empty($width) || !empty($density) || !empty($futureCompatH) || floatval($number) < 0) { + $error = true; + } + $density = $number; + } else if ($unit === 'h' && filter_var($number, FILTER_VALIDATE_INT) && intval($number) >= 0) { + if (!empty($futureCompatH) || !empty($density)) { + $error = true; + } + $futureCompatH = $number; + } else { + $error = true; + } + } + + if (!empty($futureCompatH) && empty($width)) { + $error = true; + } + + if ($error) { + return false; + } + + if ($width) { + return $width . 'w'; + } + + if ($density) { + return $density . 'x'; + } + + return ''; } } diff --git a/tests/Entity/PageContentFilteringTest.php b/tests/Entity/PageContentFilteringTest.php index 5cb898e05d4..68b597387f2 100644 --- a/tests/Entity/PageContentFilteringTest.php +++ b/tests/Entity/PageContentFilteringTest.php @@ -488,6 +488,35 @@ public function test_allow_list_style_filtering() } } + public function test_media_protocol_relative_urls_are_given_scheme_depending_on_app_url() + { + $testCasesExpectedByInput = [ + '
    My local image
    ' => '
    My local image
    ', + '' => '', + '' => '', + '' => '', + '
    My local image
    ' => '
    My local image
    ', + ]; + + $baseUrls = ['https://example.com' => 'https', 'http://example.com' => 'http']; + foreach ($baseUrls as $baseUrl => $expectedScheme) { + $this->runWithEnv(['APP_URL' => $baseUrl], function () use ($expectedScheme, $baseUrl, $testCasesExpectedByInput) { + config()->set('app.content_filtering', 'a'); + $page = $this->entities->page(); + $this->asEditor(); + + foreach ($testCasesExpectedByInput as $input => $expected) { + $page->html = $input; + $page->save(); + $resp = $this->get($page->getUrl()); + + $resp->assertSee(str_replace('SCHEME', $expectedScheme, $expected), false); + $resp->assertDontSee($input, false); + } + }); + } + } + public function test_allow_list_does_not_filter_cases() { $testCasesExpectedByInput = [ From 1732f15e4ccbfcb6bd11a62e2676b3f0bea26d30 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 27 Jul 2026 21:23:47 +0100 Subject: [PATCH 197/204] External Auth: Updated user external auth id matching to be stricter Added tests to cover. Thanks to whale120 for reporting. --- app/Access/ExternalBaseUserProvider.php | 10 ++++-- app/Access/RegistrationService.php | 4 +-- app/App/Providers/AuthServiceProvider.php | 3 +- app/Users/UserRepo.php | 19 ++++++++++++ ...pdate_users_external_auth_id_collation.php | 31 +++++++++++++++++++ tests/Auth/LdapTest.php | 30 ++++++++++++++++++ tests/Auth/OidcTest.php | 23 +++++++++++++- tests/Auth/Saml2Test.php | 23 ++++++++++++++ 8 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 database/migrations/2026_07_27_201402_update_users_external_auth_id_collation.php diff --git a/app/Access/ExternalBaseUserProvider.php b/app/Access/ExternalBaseUserProvider.php index 2165fd4591e..ef001289d11 100644 --- a/app/Access/ExternalBaseUserProvider.php +++ b/app/Access/ExternalBaseUserProvider.php @@ -3,11 +3,17 @@ namespace BookStack\Access; use BookStack\Users\Models\User; +use BookStack\Users\UserRepo; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\UserProvider; class ExternalBaseUserProvider implements UserProvider { + public function __construct( + protected UserRepo $userRepo, + ) { + } + /** * Retrieve a user by their unique identifier. */ @@ -44,9 +50,7 @@ public function updateRememberToken(Authenticatable $user, $token) */ public function retrieveByCredentials(array $credentials): ?Authenticatable { - return User::query() - ->where('external_auth_id', $credentials['external_auth_id']) - ->first(); + return $this->userRepo->getByExternalAuthId($credentials['external_auth_id']); } /** diff --git a/app/Access/RegistrationService.php b/app/Access/RegistrationService.php index e47479e7991..e3ae9b2025c 100644 --- a/app/Access/RegistrationService.php +++ b/app/Access/RegistrationService.php @@ -52,9 +52,7 @@ protected function registrationAllowed(): bool */ public function findOrRegister(string $name, string $email, string $externalId): User { - $user = User::query() - ->where('external_auth_id', '=', $externalId) - ->first(); + $user = $this->userRepo->getByExternalAuthId($externalId); if (is_null($user)) { $userData = [ diff --git a/app/App/Providers/AuthServiceProvider.php b/app/App/Providers/AuthServiceProvider.php index 6a816252131..8c71fee3ac3 100644 --- a/app/App/Providers/AuthServiceProvider.php +++ b/app/App/Providers/AuthServiceProvider.php @@ -10,6 +10,7 @@ use BookStack\Access\RegistrationService; use BookStack\Api\ApiTokenGuard; use BookStack\Users\Models\User; +use BookStack\Users\UserRepo; use Illuminate\Support\Facades\Auth; use Illuminate\Support\ServiceProvider; use Illuminate\Validation\Rules\Password; @@ -60,7 +61,7 @@ public function boot(): void public function register(): void { Auth::provider('external-users', function () { - return new ExternalBaseUserProvider(); + return new ExternalBaseUserProvider($this->app[UserRepo::class]); }); // Bind and provide the default system user as a singleton to the app instance when needed. diff --git a/app/Users/UserRepo.php b/app/Users/UserRepo.php index 2c0897ceffb..1643756c8d1 100644 --- a/app/Users/UserRepo.php +++ b/app/Users/UserRepo.php @@ -51,6 +51,25 @@ public function getBySlug(string $slug): User return User::query()->where('slug', '=', $slug)->firstOrFail(); } + /** + * Get a user by their external auth ID value. + * Returns null if no matching user found. + */ + public function getByExternalAuthId(string $externalId): User|null + { + // We only really expect at most one user from the search, but as an extra layer of defence against database + // normalisation we search possible matches exactly against the value. + $users = User::query()->where('external_auth_id', '=', $externalId)->get(); + + foreach ($users as $user) { + if ($user->external_auth_id === $externalId) { + return $user; + } + } + + return null; + } + /** * Create a new basic instance of user with the given pre-validated data. * diff --git a/database/migrations/2026_07_27_201402_update_users_external_auth_id_collation.php b/database/migrations/2026_07_27_201402_update_users_external_auth_id_collation.php new file mode 100644 index 00000000000..9f9267c8800 --- /dev/null +++ b/database/migrations/2026_07_27_201402_update_users_external_auth_id_collation.php @@ -0,0 +1,31 @@ +string('external_auth_id') + ->collation('utf8mb4_bin') + ->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->string('external_auth_id') + ->change(); + }); + } +}; diff --git a/tests/Auth/LdapTest.php b/tests/Auth/LdapTest.php index d1f128a50d9..5adbe11a1e9 100644 --- a/tests/Auth/LdapTest.php +++ b/tests/Auth/LdapTest.php @@ -207,6 +207,36 @@ public function test_a_custom_uid_attribute_can_be_specified_and_is_used_properl $this->assertDatabaseHas('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => 'cooluser456']); } + public function test_login_uses_exact_match_for_external_auth_id_values() + { + config()->set(['services.ldap.id_attribute' => 'my_custom_id']); + // External auth id the same as we expect below but different casing + User::query()->forceCreate([ + 'email' => 'otheruser@example.com', + 'external_auth_id' => 'CoolUser456', + 'email_confirmed' => true, + 'name' => 'Barry Scott', + ]); + + $this->commonLdapMocks(1, 1, 1, 2, 1); + $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn'); + $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1) + ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array')) + ->andReturn(['count' => 1, 0 => [ + 'cn' => [$this->mockUser->name], + 'dn' => $ldapDn, + 'my_custom_id' => ['cooluser456'], + 'mail' => [$this->mockUser->email], + ]]); + + $resp = $this->mockUserLogin(); + $resp->assertRedirect('/'); + + $this->assertDatabaseHas('users', ['email' => $this->mockUser->email]); + $this->assertEquals($this->mockUser->email, user()->email); + $this->assertEquals('cooluser456', user()->external_auth_id); + } + public function test_user_filter_default_placeholder_format() { config()->set('services.ldap.user_filter', '(&(uid={user}))'); diff --git a/tests/Auth/OidcTest.php b/tests/Auth/OidcTest.php index 8508568f1f4..0fcfdc5ff87 100644 --- a/tests/Auth/OidcTest.php +++ b/tests/Auth/OidcTest.php @@ -471,7 +471,28 @@ public function test_auth_uses_configured_external_id_claim_option() $this->assertEquals('xXBennyTheGeezXx', $user->external_auth_id); } - public function test_auth_uses_mulitple_display_name_claims_if_configured() + public function test_auth_uses_external_id_as_exact_value() + { + // External auth id the same as we expect below but different casing + User::query()->forceCreate([ + 'email' => 'otheruser@example.com', + 'external_auth_id' => 'Benni202', + 'email_confirmed' => true, + 'name' => 'Barry Scott', + ]); + + $resp = $this->runLogin([ + 'email' => 'benny@example.com', + 'sub' => 'benni202', + ]); + $resp->assertRedirect('/'); + + $this->assertDatabaseHas('users', ['email' => 'benny@example.com']); + $this->assertEquals('benny@example.com', user()->email); + $this->assertEquals('benni202', user()->external_auth_id); + } + + public function test_auth_uses_multiple_display_name_claims_if_configured() { config()->set(['oidc.display_name_claims' => 'first_name|last_name']); diff --git a/tests/Auth/Saml2Test.php b/tests/Auth/Saml2Test.php index 6a3063bcf51..fbaffceb38e 100644 --- a/tests/Auth/Saml2Test.php +++ b/tests/Auth/Saml2Test.php @@ -419,6 +419,29 @@ public function test_login_where_existing_non_saml_user_shows_warning() $acsPost->assertSee('A user with the email user@example.com already exists but with different credentials'); } + public function test_login_uses_exact_match_for_external_auth_values() + { + $this->post('/saml2/login'); + config()->set(['saml2.onelogin.strict' => false]); + + // Make the user pre-existing in DB with auth_id of different casing + User::query()->forceCreate([ + 'email' => 'otheruser@example.com', + 'external_auth_id' => 'UsEr', + 'email_confirmed' => true, + 'name' => 'Barry Scott', + ]); + + $this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]); + + $this->assertTrue($this->isAuthenticated()); + $this->assertDatabaseHas('users', [ + 'email' => 'user@example.com', + ]); + $this->assertEquals('user@example.com', user()->email); + $this->assertEquals('user', user()->external_auth_id); + } + public function test_login_request_contains_expected_default_authncontext() { $authReq = $this->getAuthnRequest(); From 2183fc7fc86c0aa183c5eb2068fc691aa97f576d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 28 Jul 2026 13:27:42 +0100 Subject: [PATCH 198/204] API: Limited exception details shown Updated exception handler to reduce the amount of detail shown to prevent potentially sensitive details (like internal paths) being shown in the error message. Added an interface for specifically marking exceptions whos messages we may want to show. Thanks to Tanner Marks for reporting. --- app/Exceptions/ApiAuthException.php | 7 ++- app/Exceptions/Handler.php | 19 ++++++-- app/Exceptions/NotifyException.php | 11 +++-- app/Exceptions/PermissionsException.php | 6 ++- app/Exceptions/PrettyException.php | 14 +++++- app/Exceptions/ShowsApiExceptionMessage.php | 13 +++++ tests/Api/ApiErrorTest.php | 53 +++++++++++++++++++++ tests/Api/ImageGalleryApiTest.php | 16 +++++++ 8 files changed, 128 insertions(+), 11 deletions(-) create mode 100644 app/Exceptions/ShowsApiExceptionMessage.php create mode 100644 tests/Api/ApiErrorTest.php diff --git a/app/Exceptions/ApiAuthException.php b/app/Exceptions/ApiAuthException.php index 070f7a8df0b..55a6afae6a4 100644 --- a/app/Exceptions/ApiAuthException.php +++ b/app/Exceptions/ApiAuthException.php @@ -4,7 +4,7 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; -class ApiAuthException extends \Exception implements HttpExceptionInterface +class ApiAuthException extends \Exception implements HttpExceptionInterface, ShowsApiExceptionMessage { protected int $status; @@ -23,4 +23,9 @@ public function getHeaders(): array { return []; } + + public function getMessageForApi(): string + { + return $this->getMessage(); + } } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 97a2b0a4f24..a7f23e5f41a 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -126,16 +126,25 @@ protected function renderApiException(Throwable $e): JsonResponse $headers = $e->getHeaders(); } - if ($e instanceof ModelNotFoundException) { - $code = 404; - } - $responseData = [ 'error' => [ - 'message' => $e->getMessage(), + 'message' => 'An error occurred', ], ]; + if ($e instanceof ModelNotFoundException) { + $responseData['error']['message'] = 'The requested resource could not be found.'; + $code = 404; + } + + if ($e instanceof ShowsApiExceptionMessage) { + $responseData['error']['message'] = $e->getMessageForApi(); + } + + if (app()->hasDebugModeEnabled()) { + $responseData['error']['message'] = $e->getMessage(); + } + if ($e instanceof ValidationException) { $responseData['error']['message'] = 'The given data was invalid.'; $responseData['error']['validation'] = $e->errors(); diff --git a/app/Exceptions/NotifyException.php b/app/Exceptions/NotifyException.php index d2fba30c4e9..b0514059d79 100644 --- a/app/Exceptions/NotifyException.php +++ b/app/Exceptions/NotifyException.php @@ -8,13 +8,13 @@ /** * 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. + * 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 +class NotifyException extends Exception implements Responsable, HttpExceptionInterface, ShowsApiExceptionMessage { public function __construct( string $message, @@ -61,4 +61,9 @@ public function toResponse($request) return redirect($this->redirectLocation); } + + public function getMessageForApi(): string + { + return $this->getMessage(); + } } diff --git a/app/Exceptions/PermissionsException.php b/app/Exceptions/PermissionsException.php index 64da55d21f4..ca5104df9a3 100644 --- a/app/Exceptions/PermissionsException.php +++ b/app/Exceptions/PermissionsException.php @@ -4,6 +4,10 @@ use Exception; -class PermissionsException extends Exception +class PermissionsException extends Exception implements ShowsApiExceptionMessage { + public function getMessageForApi(): string + { + return $this->getMessage(); + } } diff --git a/app/Exceptions/PrettyException.php b/app/Exceptions/PrettyException.php index 606085231f7..d8a9d315021 100644 --- a/app/Exceptions/PrettyException.php +++ b/app/Exceptions/PrettyException.php @@ -6,7 +6,7 @@ use Illuminate\Contracts\Support\Responsable; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; -class PrettyException extends Exception implements Responsable, HttpExceptionInterface +class PrettyException extends Exception implements Responsable, HttpExceptionInterface, ShowsApiExceptionMessage { protected ?string $subtitle = null; protected ?string $details = null; @@ -56,4 +56,16 @@ public function getHeaders(): array { return []; } + + public function getMessageForApi(): string + { + $message = $this->getMessage() . '.'; + if ($this->subtitle) { + $message .= " {$this->subtitle}."; + } + if ($this->details) { + $message .= " {$this->details}."; + } + return $message; + } } diff --git a/app/Exceptions/ShowsApiExceptionMessage.php b/app/Exceptions/ShowsApiExceptionMessage.php new file mode 100644 index 00000000000..66bf4388642 --- /dev/null +++ b/app/Exceptions/ShowsApiExceptionMessage.php @@ -0,0 +1,13 @@ +partialMock(SystemApiController::class); + $mockController->shouldReceive('read')->andThrow(\InvalidArgumentException::class, 'Potentially sensitive data', 500); + + $resp = $this->actingAsApiEditor()->get('/api/system'); + $resp->assertStatus(500); + $resp->assertDontSee('Potentially sensitive data', false); + $resp->assertJsonPath('error.message', 'An error occurred'); + + config(['app.debug' => true]); + + $resp = $this->actingAsApiEditor()->get('/api/system'); + $resp->assertStatus(500); + $resp->assertJsonPath('error.message', 'Potentially sensitive data'); + } + + public function test_exception_message_when_model_not_found() + { + $resp = $this->actingAsApiEditor()->get('/api/books/123456789'); + $resp->assertStatus(404); + $resp->assertSee('The requested resource could not be found.', false); + } + + public function test_pretty_exception_messages_are_provided_in_non_debug_mode() + { + $mockController = $this->partialMock(SystemApiController::class); + $exception = new PrettyException('Mr Error is here!'); + $exception->setSubtitle('Oh no!'); + $exception->setDetails('Something has really gone wrong'); + $mockController->shouldReceive('read')->andThrow($exception); + + $resp = $this->actingAsApiEditor()->get('/api/system'); + $resp->assertStatus(500); + $resp->assertJson([ + 'error' => [ + 'message' => 'Mr Error is here!. Oh no!. Something has really gone wrong.' + ] + ]); + } +} diff --git a/tests/Api/ImageGalleryApiTest.php b/tests/Api/ImageGalleryApiTest.php index 09dba84f548..3db421ce881 100644 --- a/tests/Api/ImageGalleryApiTest.php +++ b/tests/Api/ImageGalleryApiTest.php @@ -454,4 +454,20 @@ public function test_delete_endpoint_requires_image_delete_permission() $resp = $this->deleteJson($this->baseEndpoint . "/{$image->id}"); $resp->assertStatus(204); } + + public function test_delete_works_on_orphaned_image() + { + $this->actingAsApiAdmin(); + $imagePage = $this->entities->page(); + $data = $this->files->uploadGalleryImageToPage($this, $imagePage); + + $image = Image::query()->findOrFail($data['response']->id); + + $this->entities->destroy($imagePage); + + $resp = $this->deleteJson($this->baseEndpoint . "/{$image->id}"); + + $resp->assertStatus(204); + $this->assertDatabaseMissing('images', ['id' => $image->id]); + } } From 5ed685d1e6a9bb1f90344ab771a013ea90ac492e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 28 Jul 2026 13:35:42 +0100 Subject: [PATCH 199/204] Image API: Update delete permission check Aligns with ImageController permissions by checking related item view access only if that still exists. Thanks to Tanner Marks for reporting. --- app/Uploads/Controllers/ImageGalleryApiController.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/Uploads/Controllers/ImageGalleryApiController.php b/app/Uploads/Controllers/ImageGalleryApiController.php index 6a72e4c30e4..5c18f8a680a 100644 --- a/app/Uploads/Controllers/ImageGalleryApiController.php +++ b/app/Uploads/Controllers/ImageGalleryApiController.php @@ -161,8 +161,13 @@ public function update(Request $request, string $id) public function delete(string $id) { $image = $this->imageRepo->getById($id); - $this->checkOwnablePermission(Permission::PageView, $image->getPage()); $this->checkOwnablePermission(Permission::ImageDelete, $image); + + $relatedPage = $image->getPage(); + if ($relatedPage) { + $this->checkOwnablePermission(Permission::PageView, $image->getPage()); + } + $this->imageRepo->destroyImage($image); return response('', 204); From badc4f9b8e6a13e97e141119089358e7a0901bd2 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 28 Jul 2026 14:29:36 +0100 Subject: [PATCH 200/204] Gallery API: Aligned image querying Updated endpoint image querying to generally follow the same logic to ensure filtering on just the expected types which are intended to be managed by this endpoint, and it err on the side of caution in terms of access control. --- .../Controllers/ImageGalleryApiController.php | 16 +++------- app/Uploads/Image.php | 1 + app/Uploads/ImageRepo.php | 7 +++++ tests/Api/ImageGalleryApiTest.php | 31 +++++++++++++++++-- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/app/Uploads/Controllers/ImageGalleryApiController.php b/app/Uploads/Controllers/ImageGalleryApiController.php index 5c18f8a680a..abaf6939ff5 100644 --- a/app/Uploads/Controllers/ImageGalleryApiController.php +++ b/app/Uploads/Controllers/ImageGalleryApiController.php @@ -97,7 +97,7 @@ public function create(Request $request) */ public function read(string $id) { - $image = Image::query()->scopes(['visible'])->findOrFail($id); + $image = $this->imageRepo->getVisiblePageImageById($id); return response()->json($this->formatForSingleResponse($image)); } @@ -108,7 +108,7 @@ public function read(string $id) */ public function readData(string $id) { - $image = Image::query()->scopes(['visible'])->findOrFail($id); + $image = $this->imageRepo->getVisiblePageImageById($id); return $this->imageService->streamImageFromStorageResponse('gallery', $image->path); } @@ -141,8 +141,7 @@ public function readDataForUrl(Request $request) public function update(Request $request, string $id) { $data = $this->validate($request, $this->rules()['update']); - $image = $this->imageRepo->getById($id); - $this->checkOwnablePermission(Permission::PageView, $image->getPage()); + $image = $this->imageRepo->getVisiblePageImageById($id); $this->checkOwnablePermission(Permission::ImageUpdate, $image); $this->imageRepo->updateImageDetails($image, $data); @@ -160,21 +159,16 @@ public function update(Request $request, string $id) */ public function delete(string $id) { - $image = $this->imageRepo->getById($id); + $image = $this->imageRepo->getVisiblePageImageById($id); $this->checkOwnablePermission(Permission::ImageDelete, $image); - $relatedPage = $image->getPage(); - if ($relatedPage) { - $this->checkOwnablePermission(Permission::PageView, $image->getPage()); - } - $this->imageRepo->destroyImage($image); return response('', 204); } /** - * Format the given image model for single-result display. + * Format the given image model for a single-result display. */ protected function formatForSingleResponse(Image $image): array { diff --git a/app/Uploads/Image.php b/app/Uploads/Image.php index 81b6db6fd22..879e0043a1e 100644 --- a/app/Uploads/Image.php +++ b/app/Uploads/Image.php @@ -39,6 +39,7 @@ public function jointPermissions(): HasMany /** * Scope the query to just the images visible to the user based upon the * user visibility of the uploaded_to page. + * This limits results to just page-based images (gallery and drawio types). */ public function scopeVisible(Builder $query): Builder { diff --git a/app/Uploads/ImageRepo.php b/app/Uploads/ImageRepo.php index e87e22b3a3c..ef3ed9e1f89 100644 --- a/app/Uploads/ImageRepo.php +++ b/app/Uploads/ImageRepo.php @@ -27,6 +27,13 @@ public function getById($id): Image return Image::query()->findOrFail($id); } + public function getVisiblePageImageById($id): Image + { + return Image::query() + ->scopes('visible') + ->findOrFail($id); + } + /** * Execute a paginated query, returning in a standard format. * Also runs the query through the restriction system. diff --git a/tests/Api/ImageGalleryApiTest.php b/tests/Api/ImageGalleryApiTest.php index 3db421ce881..8944dcffd33 100644 --- a/tests/Api/ImageGalleryApiTest.php +++ b/tests/Api/ImageGalleryApiTest.php @@ -423,6 +423,32 @@ public function test_update_endpoint_requires_image_update_permission() $resp->assertStatus(200); } + public function test_update_endpoint_only_works_on_gallery_and_drawio_images() + { + $this->actingAsApiAdmin(); + $imagePage = $this->entities->page(); + $data = $this->files->uploadGalleryImageToPage($this, $imagePage); + $image = Image::findOrFail($data['response']->id); + + $statusByImageType = [ + 'gallery' => 200, + 'drawio' => 200, + 'cover_book' => 404, + 'user' => 404, + 'system' => 404, + ]; + + foreach ($statusByImageType as $type => $status) { + $image->type = $type; + $image->save(); + + $resp = $this->putJson($this->baseEndpoint . "/{$image->id}", [ + 'name' => "My updated {$type} image!", + ]); + $resp->assertStatus($status); + } + } + public function test_delete_endpoint() { $this->actingAsApiAdmin(); @@ -455,7 +481,7 @@ public function test_delete_endpoint_requires_image_delete_permission() $resp->assertStatus(204); } - public function test_delete_works_on_orphaned_image() + public function test_delete_limited_to_visible_images() { $this->actingAsApiAdmin(); $imagePage = $this->entities->page(); @@ -467,7 +493,6 @@ public function test_delete_works_on_orphaned_image() $resp = $this->deleteJson($this->baseEndpoint . "/{$image->id}"); - $resp->assertStatus(204); - $this->assertDatabaseMissing('images', ['id' => $image->id]); + $resp->assertStatus(404); } } From a0108ae0ea9880da8baf9c41083b4ba3db9c9523 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 28 Jul 2026 15:19:20 +0100 Subject: [PATCH 201/204] Maintenance: Fixed test issues from recent changes --- app/Access/Guards/ExternalBaseSessionGuard.php | 4 ++-- app/Access/LoginService.php | 5 ++++- app/Util/HtmlPurifier/SrcsetAttrDef.php | 6 +++--- tests/Exports/HtmlExportTest.php | 3 +-- tests/Exports/PdfExportTest.php | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/Access/Guards/ExternalBaseSessionGuard.php b/app/Access/Guards/ExternalBaseSessionGuard.php index 91239599ba9..b389031824b 100644 --- a/app/Access/Guards/ExternalBaseSessionGuard.php +++ b/app/Access/Guards/ExternalBaseSessionGuard.php @@ -30,7 +30,7 @@ class ExternalBaseSessionGuard implements StatefulGuard /** * The user we last attempted to retrieve. */ - protected Authenticatable|null $lastAttempted; + protected Authenticatable|null $lastAttempted = null; /** * The session used by the guard. @@ -203,7 +203,7 @@ protected function clearUserDataFromStorage(): void /** * Get the last user we attempted to authenticate. */ - public function getLastAttempted(): Authenticatable + public function getLastAttempted(): Authenticatable|null { return $this->lastAttempted; } diff --git a/app/Access/LoginService.php b/app/Access/LoginService.php index 6547926c559..46545f798b7 100644 --- a/app/Access/LoginService.php +++ b/app/Access/LoginService.php @@ -13,6 +13,7 @@ use BookStack\Theming\ThemeEvents; use BookStack\Users\Models\User; use Exception; +use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Support\Facades\Hash; class LoginService @@ -178,7 +179,9 @@ public function attempt(array $credentials, string $method, bool $remember = fal // Perform a dummy hash check to balance out the time of a login with an existing known user // with that of a user not in the system (which we don't perform a hash check for in the above). - if (!$result && auth()->getLastAttempted() === null) { + /** @var Authenticatable|null $lastAttempted */ + $lastAttempted = auth()->getLastAttempted(); + if (!$result && $lastAttempted === null) { Hash::check($credentials['password'], '$2y$04$A.H9icXH4/lxLd9DHuaYqO/GVBd0OKetxyY0txmNfTAlPLVnTBx3y'); } diff --git a/app/Util/HtmlPurifier/SrcsetAttrDef.php b/app/Util/HtmlPurifier/SrcsetAttrDef.php index 2f0ffffab30..783f1f86cc7 100644 --- a/app/Util/HtmlPurifier/SrcsetAttrDef.php +++ b/app/Util/HtmlPurifier/SrcsetAttrDef.php @@ -87,7 +87,7 @@ private function parseImageSources($string) } if ($state === 'in_descriptor') { - if ($c !== null && strpos($asciiWhitespace, $c) !== false) { + if ($c !== null && str_contains($asciiWhitespace, $c)) { if ($currentDescriptor !== '') { $descriptors[] = $currentDescriptor; } @@ -119,8 +119,8 @@ private function parseImageSources($string) } else { $currentDescriptor .= $c; } - } else if ($state === 'after_descriptor') { - if ($c !== null && strpos($asciiWhitespace, $c) !== false) { + } else { + if ($c !== null && str_contains($asciiWhitespace, $c)) { // Stay in this state } else if ($c === null) { break; diff --git a/tests/Exports/HtmlExportTest.php b/tests/Exports/HtmlExportTest.php index 223a8c92285..3eb04a7ee20 100644 --- a/tests/Exports/HtmlExportTest.php +++ b/tests/Exports/HtmlExportTest.php @@ -166,8 +166,7 @@ public function test_page_export_contained_html_image_fetches_only_run_when_url_ $storageDisk->delete('uploads/svg_test.svg'); $resp->assertDontSee('http://localhost/uploads/images/gallery/svg_test.svg', false); - $resp->assertSee('http://localhost/uploads/svg_test.svg'); - $resp->assertSee('src="/uploads/svg_test.svg"', false); + $resp->assertSee('src="http://localhost/uploads/svg_test.svg"', false); } public function test_page_export_contained_html_does_not_allow_upward_traversal_with_local() diff --git a/tests/Exports/PdfExportTest.php b/tests/Exports/PdfExportTest.php index 78da3b0c2cc..739406e8daa 100644 --- a/tests/Exports/PdfExportTest.php +++ b/tests/Exports/PdfExportTest.php @@ -58,7 +58,7 @@ public function test_page_pdf_export_converts_iframes_to_links() $this->asEditor()->get($page->getUrl('/export/pdf')); $this->assertStringNotContainsString('iframe>', $pdfHtml); - $this->assertStringContainsString('

    https://www.youtube.com/embed/ShqUjt33uOs

    ', $pdfHtml); + $this->assertStringContainsString('

    http://www.youtube.com/embed/ShqUjt33uOs

    ', $pdfHtml); } public function test_page_pdf_export_opens_details_blocks() From 17398590d3f5cb0ae730ef078c683c7a273f98d9 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Wed, 29 Jul 2026 04:31:29 +0000 Subject: [PATCH 202/204] New Crowdin translations by GitHub Action --- lang/fr/activities.php | 4 +- lang/fr/auth.php | 2 +- lang/fr/entities.php | 6 +-- lang/fr/preferences.php | 2 +- lang/fr/settings.php | 20 +++++----- lang/it/entities.php | 8 ++-- lang/nl/activities.php | 4 +- lang/nl/auth.php | 2 +- lang/nl/entities.php | 6 +-- lang/nl/errors.php | 2 +- lang/nl/settings.php | 10 ++--- lang/sv/activities.php | 4 +- lang/sv/auth.php | 2 +- lang/sv/entities.php | 60 ++++++++++++++-------------- lang/sv/errors.php | 20 +++++----- lang/sv/notifications.php | 4 +- lang/sv/preferences.php | 62 ++++++++++++++--------------- lang/sv/settings.php | 84 +++++++++++++++++++-------------------- lang/sv/validation.php | 10 ++--- lang/tr/auth.php | 4 +- lang/tr/common.php | 2 +- lang/tr/editor.php | 2 +- 22 files changed, 160 insertions(+), 160 deletions(-) diff --git a/lang/fr/activities.php b/lang/fr/activities.php index fcacc90898e..e0c51962ae8 100644 --- a/lang/fr/activities.php +++ b/lang/fr/activities.php @@ -99,8 +99,8 @@ 'user_update_notification' => 'Utilisateur mis à jour avec succès', 'user_delete' => 'utilisateur supprimé', 'user_delete_notification' => 'Utilisateur supprimé avec succès', - 'user_mfa_reset' => 'reset MFA for user', - 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', + 'user_mfa_reset' => 'réinitialiser l\'authentification multifacteur pour l\'utilisateur', + 'user_mfa_reset_notification' => 'Les méthodes d\'authentification multifacteurs sont réinitialisées', // API Tokens 'api_token_create' => 'a créé un jeton API', diff --git a/lang/fr/auth.php b/lang/fr/auth.php index a7fe59d0ccb..e2f94904877 100644 --- a/lang/fr/auth.php +++ b/lang/fr/auth.php @@ -8,7 +8,7 @@ 'failed' => 'Ces informations ne correspondent à aucun compte.', 'throttle' => 'Trop d\'essais, veuillez réessayer dans :seconds secondes.', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Trop de tentatives de vérification multifactorielle. Veuillez réessayer dans :secondes secondes.', // Login & Register 'sign_up' => 'S\'inscrire', diff --git a/lang/fr/entities.php b/lang/fr/entities.php index fa0808912a7..3479ab57221 100644 --- a/lang/fr/entities.php +++ b/lang/fr/entities.php @@ -331,9 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Afficher/masquer la barre latérale', - '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_contents' => 'Contenu de la page', + 'page_contents_none' => 'Aucun titre n\'a été trouvé dans le contenu de la page.', + 'page_contents_info' => 'Le menu de contenu est généré à partir de tous les formats de titres utilisés dans la page.', 'page_tags' => 'Étiquettes de la page', 'chapter_tags' => 'Étiquettes du chapitre', 'book_tags' => 'Étiquettes du livre', diff --git a/lang/fr/preferences.php b/lang/fr/preferences.php index dbae975f31d..b16d5acf2df 100644 --- a/lang/fr/preferences.php +++ b/lang/fr/preferences.php @@ -15,7 +15,7 @@ 'shortcuts_section_navigation' => 'Navigation', 'shortcuts_section_actions' => 'Actions communes', 'shortcuts_save' => 'Sauvegarder les raccourcis', - 'shortcuts_overlay_desc' => 'Note : Lorsque les raccourcis sont activés, assistant est disponible en appuyant sur "?" qui mettra en surbrillance les raccourcis disponibles pour les actions actuellement visibles à l\'écran.', + 'shortcuts_overlay_desc' => 'Note : Lorsque les raccourcis sont activés, assistant est disponible en appuyant sur «?» qui mettra en surbrillance les raccourcis disponibles pour les actions actuellement visibles à l\'écran.', 'shortcuts_update_success' => 'Les préférences de raccourci ont été mises à jour !', 'shortcuts_overview_desc' => 'Gérer les raccourcis clavier que vous pouvez utiliser pour naviguer dans l\'interface utilisateur du système.', diff --git a/lang/fr/settings.php b/lang/fr/settings.php index 697e5ffa06d..cd2368db18c 100644 --- a/lang/fr/settings.php +++ b/lang/fr/settings.php @@ -39,7 +39,7 @@ 'app_homepage_desc' => 'Choisissez une page à afficher sur la page d\'accueil au lieu de la vue par défaut. Les permissions sont ignorées pour les pages sélectionnées.', 'app_homepage_select' => 'Choisissez une page', 'app_footer_links' => 'Liens de pied de page', - 'app_footer_links_desc' => 'Ajoutez des liens dans le pied de page du site. Ils seront affichés en bas de la plupart des pages, incluant celles qui ne nécesittent pas de connexion. Vous pouvez utiliser l\'étiquette "trans::" pour utiliser les traductions définies par le système. Par exemple, utiliser "trans::common.privacy_policy" fournira la traduction de "Politique de Confidentalité" et "trans::common.terms_of_service" fournira la traduction de "Conditions d\'utilisation".', + 'app_footer_links_desc' => 'Ajoutez des liens dans le pied de page du site. Ils seront affichés en bas de la plupart des pages, incluant celles qui ne nécessitent pas de connexion. Vous pouvez utiliser l\'étiquette "trans::" pour utiliser les traductions définies par le système. Par exemple, utiliser "trans::common.privacy_policy" fournira la traduction de "Politique de Confidentalité" et "trans::common.terms_of_service" fournira la traduction de "Conditions d\'utilisation".', 'app_footer_links_label' => 'Libellé du lien', 'app_footer_links_url' => 'URL du lien', 'app_footer_links_add' => 'Ajouter un lien en pied de page', @@ -61,17 +61,17 @@ 'page_draft_color' => 'Couleur des brouillons', // Registration Settings - 'reg_settings' => 'Préférence pour l\'inscription', + 'reg_settings' => 'Paramètres d\'inscription', 'reg_enable' => 'Activer l\'inscription', 'reg_enable_toggle' => 'Activer l\'inscription', - 'reg_enable_desc' => 'Lorsque l\'inscription est activée, l\'utilisateur pourra s\'enregistrer en tant qu\'utilisateur de l\'application. Lors de l\'inscription, ils se voient attribuer un rôle par défaut.', - 'reg_default_role' => 'Rôle par défaut lors de l\'inscription', + 'reg_enable_desc' => 'Lorsque l\'inscription est activée, l\'utilisateur peut s\'inscrire lui-même en tant qu\'utilisateur de l\'application. Lors de son inscription, il se voit attribuer un rôle unique par défaut.', + 'reg_default_role' => 'Rôle de l\'utilisateur par défaut après l\'inscription', 'reg_enable_external_warning' => 'L\'option ci-dessus est ignorée lorsque l\'authentification externe LDAP ou SAML est activée. Les comptes utilisateur pour les membres non existants seront créés automatiquement si l\'authentification, par rapport au système externe utilisé, est réussie.', 'reg_email_confirmation' => 'Confirmation de l\'e-mail', 'reg_email_confirmation_toggle' => 'Obliger la confirmation par e-mail ?', 'reg_confirm_email_desc' => 'Si la restriction de domaine est activée, la confirmation sera automatiquement obligatoire et cette valeur sera ignorée.', - 'reg_confirm_restrict_domain' => 'Restreindre l\'inscription à un domaine', - 'reg_confirm_restrict_domain_desc' => 'Entrez une liste de domaines acceptés lors de l\'inscription, séparés par une virgule. Les utilisateurs recevront un e-mail de confirmation à cette adresse.
    Les utilisateurs pourront changer leur adresse après inscription s\'ils le souhaitent.', + 'reg_confirm_restrict_domain' => 'Restriction de domaine', + 'reg_confirm_restrict_domain_desc' => 'Indiquez, séparés par des virgules, les domaines de messagerie autorisés pour l\'inscription. Les utilisateurs recevront un e-mail pour confirmer leur adresse avant de pouvoir utiliser l\'application.
    Notez qu\'ils pourront modifier leur adresse e-mail après leur inscription.', 'reg_confirm_restrict_domain_placeholder' => 'Aucune restriction en place', // Sorting Settings @@ -207,7 +207,7 @@ 'role_all' => 'Tous', 'role_own' => 'Propres', 'role_controlled_by_asset' => 'Contrôlé par les ressources les ayant envoyés', - 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', + 'role_controlled_by_page_delete' => 'Contrôlé par les autorisations de suppression de page', 'role_save' => 'Enregistrer le rôle', 'role_users' => 'Utilisateurs ayant ce rôle', 'role_users_none' => 'Aucun utilisateur avec ce rôle actuellement', @@ -264,9 +264,9 @@ 'users_mfa_desc' => 'Configurer l\'authentification multi-facteurs ajoute une couche supplémentaire de sécurité à votre compte utilisateur.', 'users_mfa_x_methods' => ':count méthode configurée|:count méthodes configurées', 'users_mfa_configure' => 'Méthode de configuration', - '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?', + 'users_mfa_reset' => 'Réinitialiser les méthodes d\'authentification multifacteurs', + 'users_mfa_reset_desc' => 'Cette action réinitialisera et supprimera toutes les méthodes d\'authentification multifacteurs configurées pour cet utilisateur. Si l\'authentification multifacteurs est requise par l\'un de ses rôles, il sera invité à configurer de nouvelles méthodes lors de sa prochaine connexion.', + 'users_mfa_reset_confirm' => 'Êtes-vous sûr de vouloir réinitialiser l\'authentification multifacteurs pour cet utilisateur ?', // API Tokens 'user_api_token_create' => 'Créer un nouveau jeton API', diff --git a/lang/it/entities.php b/lang/it/entities.php index 89e1cd98831..fefb9d34b08 100644 --- a/lang/it/entities.php +++ b/lang/it/entities.php @@ -173,7 +173,7 @@ 'books_sort_desc' => 'Spostare i capitoli e le pagine di un libro per riorganizzarne il contenuto. Possono essere aggiunti altri libri che permettono di spostare facilmente capitoli e pagine tra i libri. Opzionalmente una regola di ordinamento automatico può essere impostata per ordinare automaticamente i contenuti di questo libro in caso di modifiche.', 'books_sort_auto_sort' => 'Opzione Ordinamento Automatico', 'books_sort_auto_sort_active' => 'Ordinamento Automatico Attivo: :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_auto_sort_creation_hint' => 'Le regole delle opzioni di ordinamento automatico possono essere create nell\'area delle impostazioni "Elenchi e ordinamento" da un utente con le relative autorizzazioni.', 'books_sort_named' => 'Ordina il libro :bookName', 'books_sort_name' => 'Ordina per Nome', 'books_sort_created' => 'Ordina per Data di creazione', @@ -331,9 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Attiva/disattiva barra laterale', - '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_contents' => 'Contenuto della pagina', + 'page_contents_none' => 'Nessun titolo trovato nel contenuto della pagina.', + 'page_contents_info' => 'Il sommario viene generato sulla base dei formati di intestazione utilizzati nella pagina.', 'page_tags' => 'Tag pagina', 'chapter_tags' => 'Tag capitolo', 'book_tags' => 'Tag libro', diff --git a/lang/nl/activities.php b/lang/nl/activities.php index 55356966ddc..23519f9c9d7 100644 --- a/lang/nl/activities.php +++ b/lang/nl/activities.php @@ -99,8 +99,8 @@ 'user_update_notification' => 'Gebruiker succesvol bijgewerkt', 'user_delete' => 'verwijderde gebruiker', 'user_delete_notification' => 'Gebruiker succesvol verwijderd', - 'user_mfa_reset' => 'reset MFA for user', - 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', + 'user_mfa_reset' => 'herstel meervoudige verificatie voor gebruiker', + 'user_mfa_reset_notification' => 'Meervoudige verificatie methodes hersteld', // API Tokens 'api_token_create' => 'API-token aangemaakt', diff --git a/lang/nl/auth.php b/lang/nl/auth.php index 49d04dd4fbf..3f67f8a6233 100644 --- a/lang/nl/auth.php +++ b/lang/nl/auth.php @@ -8,7 +8,7 @@ 'failed' => 'Deze inloggegevens zijn niet bij ons bekend.', 'throttle' => 'Te veel inlogpogingen! Probeer het opnieuw na :seconds seconden.', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Te veel pogingen om te verifiëren met meervoudige verificatie. Probeer het opnieuw na :seconds seconden.', // Login & Register 'sign_up' => 'Registreer', diff --git a/lang/nl/entities.php b/lang/nl/entities.php index 3695277a71e..6f5f7a37b7a 100644 --- a/lang/nl/entities.php +++ b/lang/nl/entities.php @@ -173,7 +173,7 @@ 'books_sort_desc' => 'Verplaats hoofdstukken en pagina\'s door het boek om ze te organiseren. Andere boeken kunnen worden toegevoegd zodat hoofdstukken en pagina\'s gemakkelijk tussen boeken kunnen worden verplaatst. Het is mogelijk om een automatische sorteerregel in te stellen die de inhoud zal sorteren bij wijzigingen.', 'books_sort_auto_sort' => 'Automatisch Sorteren', 'books_sort_auto_sort_active' => 'Automatisch Sorteren Actief: :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_auto_sort_creation_hint' => 'Regels voor automatisch sorteren kunnen worden aangemaakt door een bevoegde gebruiker in het gedeelte "Lijsten & Sorteren" van de instellingen.', 'books_sort_named' => 'Sorteer boek :bookName', 'books_sort_name' => 'Sorteren op naam', 'books_sort_created' => 'Sorteren op datum van aanmaken', @@ -332,8 +332,8 @@ // Editor Sidebar 'toggle_sidebar' => 'Zijbalk Tonen/Verbergen', 'page_contents' => 'Pagina Inhoud', - '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_contents_none' => 'Geen koppen gevonden binnen de inhoud van deze pagina.', + 'page_contents_info' => 'Het inhoudsmenu wordt gemaakt van alle koppen op een pagina.', 'page_tags' => 'Pagina Labels', 'chapter_tags' => 'Hoofdstuk Labels', 'book_tags' => 'Boek Labels', diff --git a/lang/nl/errors.php b/lang/nl/errors.php index 0e478fd5ac6..8dc197efe3e 100644 --- a/lang/nl/errors.php +++ b/lang/nl/errors.php @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => 'Het opgegeven geheim voor de API-token is onjuist', 'api_user_no_api_permission' => 'De eigenaar van de gebruikte API-token heeft geen machtiging om API calls te maken', 'api_user_token_expired' => 'De gebruikte autorisatie token is verlopen', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Alleen GET verzoeken zijn toegestaan wanneer de API wordt gebruikt met cookie-gebaseerde authenticatie', // Settings & Maintenance 'maintenance_test_email_failure' => 'Fout opgetreden bij het verzenden van een test email:', diff --git a/lang/nl/settings.php b/lang/nl/settings.php index 977aa55fcaa..0d0ddfa674a 100644 --- a/lang/nl/settings.php +++ b/lang/nl/settings.php @@ -104,7 +104,7 @@ 'sort_rule_op_chapters_first' => 'Hoofdstukken Eerst', 'sort_rule_op_chapters_last' => 'Hoofdstukken Laatst', 'sorting_page_limits' => 'Weergavelimiet Per Pagina', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sorting_page_limits_desc' => 'Stel in hoeveel items er op een pagina worden laten zien in de verschillende lijstweergaves. Een lager aantal verbeterd de snelheid, een hoger aantal verminderd het doorklikken door pagina\'s. Het wordt aanbevolen om een meervoud van 6 te gebruiken.', // Maintenance settings 'maint' => 'Onderhoud', @@ -207,7 +207,7 @@ 'role_all' => 'Alles', 'role_own' => 'Eigen', 'role_controlled_by_asset' => 'Gecontroleerd door de asset waar deze is geüpload', - 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', + 'role_controlled_by_page_delete' => 'Ingesteld volgens pagina verwijder machtigingen', 'role_save' => 'Rol Opslaan', 'role_users' => 'Gebruikers in deze rol', 'role_users_none' => 'Geen enkele gebruiker heeft deze rol', @@ -264,9 +264,9 @@ 'users_mfa_desc' => 'Stel meervoudige verificatie in als extra beveiligingslaag voor je gebruikersaccount.', 'users_mfa_x_methods' => ':count methode geconfigureerd|:count methoden geconfigureerd', 'users_mfa_configure' => 'Configureer methoden', - '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?', + 'users_mfa_reset' => 'Herstel Meervoudige Verificatie Methodes', + 'users_mfa_reset_desc' => 'Dit zal alle methodes voor meervoudige verificatie van deze gebruiker wissen. Als meervoudige verificatie vereist is vanwege een van hun rollen, worden ze bij hun volgende inlogpoging gevraagd om nieuwe methodes te configureren.', + 'users_mfa_reset_confirm' => 'Weet je zeker dat je de meervoudige verificatie van deze gebruiker wilt herstellen?', // API Tokens 'user_api_token_create' => 'API-token aanmaken', diff --git a/lang/sv/activities.php b/lang/sv/activities.php index c501c675273..c7afc0bace2 100644 --- a/lang/sv/activities.php +++ b/lang/sv/activities.php @@ -99,8 +99,8 @@ 'user_update_notification' => 'Användaren har uppdaterats', 'user_delete' => 'raderad användare', 'user_delete_notification' => 'Användaren har tagits bort', - 'user_mfa_reset' => 'reset MFA for user', - 'user_mfa_reset_notification' => 'Multi-factor authentication methods reset', + 'user_mfa_reset' => 'återställ MFA för användare', + 'user_mfa_reset_notification' => 'Metoder för multifaktorautentisering återställda', // API Tokens 'api_token_create' => 'skapade API-token', diff --git a/lang/sv/auth.php b/lang/sv/auth.php index 6c94f21f9e0..44275e787d8 100644 --- a/lang/sv/auth.php +++ b/lang/sv/auth.php @@ -8,7 +8,7 @@ 'failed' => 'Uppgifterna stämmer inte överens med våra register.', 'throttle' => 'För många inloggningsförsök. Prova igen om :seconds sekunder.', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'För många försök till multifaktorverifiering. Försök igen om :seconds sekunder.', // Login & Register 'sign_up' => 'Skapa konto', diff --git a/lang/sv/entities.php b/lang/sv/entities.php index 1df81684913..6a0e2f0995b 100644 --- a/lang/sv/entities.php +++ b/lang/sv/entities.php @@ -63,10 +63,10 @@ 'import_delete_desc' => 'Detta kommer att ta bort den uppladdade ZIP-baserade importfilen och kan inte ångras.', 'import_errors' => 'Importfel', 'import_errors_desc' => 'Följande fel inträffade under importförsöket:', - 'breadcrumb_siblings_for_page' => 'Navigate siblings for page', - 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter', - 'breadcrumb_siblings_for_book' => 'Navigate siblings for book', - 'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf', + 'breadcrumb_siblings_for_page' => 'Navigera mellan syskon för sida', + 'breadcrumb_siblings_for_chapter' => 'Navigera mellan syskon för kapitel', + 'breadcrumb_siblings_for_book' => 'Navigera mellan syskon för bok', + 'breadcrumb_siblings_for_bookshelf' => 'Navigera mellan syskon för hylla', // Permissions and restrictions 'permissions' => 'Rättigheter', @@ -173,7 +173,7 @@ 'books_sort_desc' => 'Flytta kapitel och sidor inom en bok för att omorganisera dess innehåll. Andra böcker kan läggas till, vilket gör det enkelt att flytta kapitel och sidor mellan böcker. Du kan även ställa in en regel som automatiskt sorterar bokens innehåll vid ändringar.', 'books_sort_auto_sort' => 'Automatiskt sorteringsalternativ', 'books_sort_auto_sort_active' => 'Aktiv automatisk sorteringsregel: :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_auto_sort_creation_hint' => 'Regler för automatisk sortering kan skapas i inställningsområdet "Listor och sortering" av en användare med relevanta behörigheter.', 'books_sort_named' => 'Sortera boken :bookName', 'books_sort_name' => 'Sortera utifrån namn', 'books_sort_created' => 'Sortera utifrån skapelse', @@ -253,7 +253,7 @@ 'pages_edit_switch_to_markdown_stable' => '(Stabilt innehåll)', 'pages_edit_switch_to_wysiwyg' => 'Växla till WYSIWYG-redigerare', 'pages_edit_switch_to_new_wysiwyg' => 'Växla till ny WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(I betatestning)', 'pages_edit_set_changelog' => 'Beskriv dina ändringar', 'pages_edit_enter_changelog_desc' => 'Ange en kort beskrivning av de ändringar du har gjort', 'pages_edit_enter_changelog' => 'Ändringslogg', @@ -272,8 +272,8 @@ 'pages_md_insert_link' => 'Infoga länk', 'pages_md_insert_drawing' => 'Infoga teckning', 'pages_md_show_preview' => 'Visa förhandsgranskning', - 'pages_md_sync_scroll' => 'Sync preview scroll', - 'pages_md_plain_editor' => 'Plaintext editor', + 'pages_md_sync_scroll' => 'Synkronisera förhandsgranskningsrullning', + 'pages_md_plain_editor' => 'Textredigerare (plaintext)', 'pages_drawing_unsaved' => 'Osparad ritning hittades', 'pages_drawing_unsaved_confirm' => 'Osparade ritningsdata hittades från ett tidigare misslyckat sparförsök. Vill du återställa och fortsätta redigera den osparade ritningen?', 'pages_not_in_chapter' => 'Sidan ligger inte i något kapitel', @@ -306,10 +306,10 @@ 'pages_edit_content_link' => 'Hoppa till sektionen i redigeraren', 'pages_pointer_enter_mode' => 'Ange markeringsläge för sektion', 'pages_pointer_label' => 'Alternativ för sidsektion', - 'pages_pointer_permalink' => 'Page Section Permalink', - 'pages_pointer_include_tag' => 'Page Section Include Tag', - 'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag', - 'pages_pointer_toggle_include' => 'Include tag mode, Press to show permalink', + 'pages_pointer_permalink' => 'Permalänk för sidavsnitt', + 'pages_pointer_include_tag' => 'Include-tagg för sidavsnitt', + 'pages_pointer_toggle_link' => 'Permalänksläge, tryck för att visa include-tagg', + 'pages_pointer_toggle_include' => 'Include-taggläge, tryck för att visa permalänk', 'pages_permissions_active' => 'Anpassade rättigheter är i bruk', 'pages_initial_revision' => 'Första publicering', 'pages_references_update_revision' => 'Automatisk uppdatering av interna länkar', @@ -331,9 +331,9 @@ // Editor Sidebar 'toggle_sidebar' => 'Visa/Dölj sidopanel', - '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_contents' => 'Sidans innehåll', + 'page_contents_none' => 'Inga rubriker hittades i sidans innehåll.', + 'page_contents_info' => 'Innehållsmenyn genereras utifrån de rubrikformat som används på sidan.', 'page_tags' => 'Sidtaggar', 'chapter_tags' => 'Kapiteltaggar', 'book_tags' => 'Boktaggar', @@ -361,7 +361,7 @@ 'attachments_explain_instant_save' => 'Ändringar här sparas omgående.', 'attachments_upload' => 'Ladda upp fil', 'attachments_link' => 'Bifoga länk', - 'attachments_upload_drop' => 'Alternatively you can drag and drop a file here to upload it as an attachment.', + 'attachments_upload_drop' => 'Alternativt kan du dra och släppa en fil här för att ladda upp den som en bilaga.', 'attachments_set_link' => 'Ange länk', 'attachments_delete' => 'Är du säker på att du vill ta bort bilagan?', 'attachments_dropzone' => 'Släpp filer här för uppladdning', @@ -403,9 +403,9 @@ 'comment_add' => 'Lägg till kommentar', 'comment_none' => 'Inga kommentarer att visa', 'comment_placeholder' => 'Lämna en kommentar här', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', + 'comment_thread_count' => ':count kommentarstråd|:count kommentarstrådar', + 'comment_archived_count' => ':count arkiverad(e)', + 'comment_archived_threads' => 'Arkiverade trådar', 'comment_save' => 'Spara kommentar', 'comment_new' => 'Ny kommentar', 'comment_created' => 'kommenterade :createDiff', @@ -415,7 +415,7 @@ 'comment_created_success' => 'Kommentaren har sparats', 'comment_updated_success' => 'Kommentaren har uppdaterats', 'comment_archive_success' => 'Arkivera kommentar', - 'comment_unarchive_success' => 'Comment un-archived', + 'comment_unarchive_success' => 'Kommentar avarkiverad', 'comment_view' => 'Visa kommentar', 'comment_jump_to_thread' => 'Hoppa till tråd', 'comment_delete_confirm' => 'Är du säker på att du vill ta bort den här kommentaren?', @@ -452,12 +452,12 @@ // References 'references' => 'Referenser', 'references_none' => 'Det finns inga referenser kopplade till detta objekt.', - 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', + 'references_to_desc' => 'Nedan listas allt känt innehåll i systemet som länkar till detta objekt.', // Watch Options 'watch' => 'Följ', 'watch_title_default' => 'Standardinställningar', - 'watch_desc_default' => 'Revert watching to just your default notification preferences.', + 'watch_desc_default' => 'Återställ bevakning till enbart dina standardaviseringsinställningar.', 'watch_title_ignore' => 'Ignorera', 'watch_desc_ignore' => 'Ignorera samtliga meddelanden, även sådana som styrs av användarens egna inställningar.', 'watch_title_new' => 'Nya sidor', @@ -465,16 +465,16 @@ 'watch_title_updates' => 'Alla siduppdateringar', 'watch_desc_updates' => 'Meddela vid alla nya sidor och siduppdateringar.', 'watch_desc_updates_page' => 'Meddela alla siduppdateringar.', - 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_title_comments' => 'Alla sidupdateringar och kommentarer', 'watch_desc_comments' => 'Meddela vid alla nya sidor, siduppdateringar och nya kommentarer.', 'watch_desc_comments_page' => 'Meddela vid siduppdateringar och nya kommentarer.', 'watch_change_default' => 'Ändra standardinställningar för meddelanden', 'watch_detail_ignore' => 'Ignorera meddelanden', - 'watch_detail_new' => 'Watching for new pages', - 'watch_detail_updates' => 'Watching new pages and updates', - 'watch_detail_comments' => 'Watching new pages, updates & comments', - 'watch_detail_parent_book' => 'Watching via parent book', - 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', - 'watch_detail_parent_chapter' => 'Watching via parent chapter', - 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', + 'watch_detail_new' => 'Bevakar nya sidor', + 'watch_detail_updates' => 'Bevakar nya sidor och uppdateringar', + 'watch_detail_comments' => 'Bevakar nya sidor, uppdateringar och kommentarer', + 'watch_detail_parent_book' => 'Bevakar via överordnad bok', + 'watch_detail_parent_book_ignore' => 'Ignorerar via överordnad bok', + 'watch_detail_parent_chapter' => 'Bevakar via överordnat kapitel', + 'watch_detail_parent_chapter_ignore' => 'Ignorerar via överordnat kapitel', ]; diff --git a/lang/sv/errors.php b/lang/sv/errors.php index 0548f96fd25..7cbe9f6c561 100644 --- a/lang/sv/errors.php +++ b/lang/sv/errors.php @@ -10,7 +10,7 @@ // Auth 'error_user_exists_different_creds' => 'En användare med adressen :email finns redan.', - 'auth_pre_register_theme_prevention' => 'User account could not be registered for the provided details', + 'auth_pre_register_theme_prevention' => 'Användarkontot kunde inte registreras med de angivna uppgifterna', 'email_already_confirmed' => 'E-posten har redan bekräftats, prova att logga in.', 'email_confirmation_invalid' => 'Denna bekräftelsekod är inte giltig eller har redan använts. Vänligen prova att registrera dig på nytt.', 'email_confirmation_expired' => 'Denna bekräftelsekod har gått ut. Vi har skickat dig en ny.', @@ -51,18 +51,18 @@ 'image_upload_error' => 'Ett fel inträffade vid uppladdningen', 'image_upload_type_error' => 'Filtypen du försöker ladda upp är ogiltig', 'image_upload_replace_type' => 'Bilder som skall ersättas måste vara av samma filtyp', - 'image_upload_memory_limit' => 'Failed to handle image upload and/or create thumbnails due to system resource limits.', - 'image_thumbnail_memory_limit' => 'Failed to create image size variations due to system resource limits.', + 'image_upload_memory_limit' => 'Det gick inte att hantera bilduppladdningen och/eller skapa miniatyrbilder på grund av begränsade systemresurser.', + 'image_thumbnail_memory_limit' => 'Det gick inte att skapa bildstorleksvarianter på grund av begränsade systemresurser.', 'image_gallery_thumbnail_memory_limit' => 'Misslyckades att skapa galleriminiatyrer på grund av otillräckliga systemresurser.', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'drawing_data_not_found' => 'Ritningsdata kunde inte laddas. Ritningen kanske inte längre finns, eller så har du inte behörighet att komma åt den.', // Attachments 'attachment_not_found' => 'Bilagan hittades ej', - 'attachment_upload_error' => 'An error occurred uploading the attachment file', + 'attachment_upload_error' => 'Ett fel uppstod vid uppladdning av bilagan', // Pages 'page_draft_autosave_fail' => 'Kunde inte spara utkastet. Kontrollera att du är ansluten till internet.', - 'page_draft_delete_fail' => 'Failed to delete page draft and fetch current page saved content', + 'page_draft_delete_fail' => 'Det gick inte att radera utkastet och hämta det sparade innehållet för aktuell sida', 'page_custom_home_deletion' => 'Det går inte att ta bort sidan medan den används som startsida', // Entities @@ -78,7 +78,7 @@ // Users 'users_cannot_delete_only_admin' => 'Du kan inte ta bort den enda admin-användaren', 'users_cannot_delete_guest' => 'Du kan inte ta bort gästanvändaren', - 'users_could_not_send_invite' => 'Could not create user since invite email failed to send', + 'users_could_not_send_invite' => 'Kunde inte skapa användare eftersom inbjudningsmejlet inte kunde skickas', // Roles 'role_cannot_be_edited' => 'Den här rollen kan inte redigeras', @@ -108,8 +108,8 @@ // Import 'import_zip_cant_read' => 'Kunde inte läsa ZIP-filen.', 'import_zip_cant_decode_data' => 'Kunde inte hitta och avkoda ZIP data.json innehåll.', - 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', - 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.', + 'import_zip_no_data' => 'ZIP-filens data innehåller inte förväntat bok-, kapitel- eller sidinnehåll.', + 'import_zip_data_too_large' => 'Innehållet i ZIP-filens data.json överskrider den konfigurerade maxgränsen för uppladdning i applikationen.', 'import_validation_failed' => 'ZIP-filen kunde inte valideras med fel:', 'import_zip_failed_notification' => 'Det gick inte att importera ZIP-fil.', 'import_perms_books' => 'Du saknar behörighet att skapa böcker.', @@ -125,7 +125,7 @@ 'api_incorrect_token_secret' => 'Hemligheten för den angivna API-token är felaktig', 'api_user_no_api_permission' => 'Ägaren av den använda API-token har inte behörighet att göra API-anrop', 'api_user_token_expired' => 'Den använda auktoriseringstoken har löpt ut', - 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication', + 'api_cookie_auth_only_get' => 'Endast GET-förfrågningar är tillåtna vid API-användning med cookiebaserad autentisering', // Settings & Maintenance 'maintenance_test_email_failure' => 'Ett fel uppstod när ett test mail skulle skickas:', diff --git a/lang/sv/notifications.php b/lang/sv/notifications.php index 19933c049cb..cf1a87cf656 100644 --- a/lang/sv/notifications.php +++ b/lang/sv/notifications.php @@ -11,8 +11,8 @@ 'updated_page_subject' => 'Uppdaterad sida: :pageName', 'updated_page_intro' => 'En sida har blivit uppdaterad i :appName:', 'updated_page_debounce' => 'För att förhindra en massa notiser, så kommer det inte skickas nya notiser på ett tag för ytterligare ändringar till denna sida av samma skribent.', - 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName', - 'comment_mention_intro' => 'You were mentioned in a comment on :appName:', + 'comment_mention_subject' => 'Du har blivit nämnd i en kommentar på sidan: :pageName', + 'comment_mention_intro' => 'Du har blivit nämnd i en kommentar på :appName:', 'detail_page_name' => 'Sidonamn:', 'detail_page_path' => 'Sidosökväg:', diff --git a/lang/sv/preferences.php b/lang/sv/preferences.php index 7ebf2681350..80dc907184e 100644 --- a/lang/sv/preferences.php +++ b/lang/sv/preferences.php @@ -8,45 +8,45 @@ 'my_account' => 'Mitt Konto', 'shortcuts' => 'Genvägar', - 'shortcuts_interface' => 'UI Shortcut Preferences', - 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', - 'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.', - 'shortcuts_toggle_label' => 'Keyboard shortcuts enabled', - 'shortcuts_section_navigation' => 'Navigation', - 'shortcuts_section_actions' => 'Common Actions', + 'shortcuts_interface' => 'Inställningar för UI-genvägar', + 'shortcuts_toggle_desc' => 'Här kan du aktivera eller inaktivera tangentbordsgenvägar för systemgränssnittet, som används för navigering och åtgärder.', + 'shortcuts_customize_desc' => 'Du kan anpassa varje genväg nedan. Tryck bara på önskad tangentkombination efter att ha valt inmatningsfältet för en genväg.', + 'shortcuts_toggle_label' => 'Tangentbordsgenvägar aktiverade', + 'shortcuts_section_navigation' => 'Navigering', + 'shortcuts_section_actions' => 'Vanliga åtgärder', 'shortcuts_save' => 'Spara genvägar', - 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', - 'shortcuts_update_success' => 'Shortcut preferences have been updated!', - 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + 'shortcuts_overlay_desc' => 'Obs: När genvägar är aktiverade finns en hjälpöverlagring tillgänglig genom att trycka på "?", vilken markerar de tillgängliga genvägarna för åtgärder som för närvarande syns på skärmen.', + 'shortcuts_update_success' => 'Genvägsinställningarna har uppdaterats!', + 'shortcuts_overview_desc' => 'Hantera tangentbordsgenvägar som du kan använda för att navigera i systemets användargränssnitt.', - 'notifications' => 'Notification Preferences', - 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', - 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', - 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', - 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment', - 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', - 'notifications_save' => 'Save Preferences', - 'notifications_update_success' => 'Notification preferences have been updated!', - 'notifications_watched' => 'Watched & Ignored Items', - 'notifications_watched_desc' => 'Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + 'notifications' => 'Aviseringsinställningar', + 'notifications_desc' => 'Kontrollera de e-postaviseringar du får när viss aktivitet utförs i systemet.', + 'notifications_opt_own_page_changes' => 'Meddela vid ändringar på sidor jag äger', + 'notifications_opt_own_page_comments' => 'Meddela vid kommentarer på sidor jag äger', + 'notifications_opt_comment_mentions' => 'Meddela när jag blir nämnd i en kommentar', + 'notifications_opt_comment_replies' => 'Meddela vid svar på mina kommentarer', + 'notifications_save' => 'Spara inställningar', + 'notifications_update_success' => 'Aviseringsinställningarna har uppdaterats!', + 'notifications_watched' => 'Bevakade och ignorerade objekt', + 'notifications_watched_desc' => 'Nedan visas de objekt som har anpassade bevakningsinställningar. För att uppdatera dina inställningar för dessa, öppna objektet och hitta bevakningsalternativen i sidopanelen.', - 'auth' => 'Access & Security', - 'auth_change_password' => 'Change Password', - 'auth_change_password_desc' => 'Change the password you use to log-in to the application. This must be at least 8 characters long.', + 'auth' => 'Åtkomst och säkerhet', + 'auth_change_password' => 'Ändra lösenord', + 'auth_change_password_desc' => 'Ändra lösenordet du använder för att logga in i applikationen. Det måste vara minst 8 tecken långt.', 'auth_change_password_success' => 'Lösenordet har uppdaterats!', 'profile' => 'Profildetaljer', - 'profile_desc' => 'Manage the details of your account which represents you to other users, in addition to details that are used for communication and system personalisation.', + 'profile_desc' => 'Hantera detaljerna för ditt konto som representerar dig för andra användare, utöver de uppgifter som används för kommunikation och systempersonalisering.', 'profile_view_public' => 'Visa publik profil', - 'profile_name_desc' => 'Configure your display name which will be visible to other users in the system through the activity you perform, and content you own.', - 'profile_email_desc' => 'This email will be used for notifications and, depending on active system authentication, system access.', - 'profile_email_no_permission' => 'Unfortunately you don\'t have permission to change your email address. If you want to change this, you\'d need to ask an administrator to change this for you.', - 'profile_avatar_desc' => 'Select an image which will be used to represent yourself to others in the system. Ideally this image should be square and about 256px in width and height.', - 'profile_admin_options' => 'Administrator Options', - 'profile_admin_options_desc' => 'Additional administrator-level options, like those to manage role assignments, can be found for your user account in the "Settings > Users" area of the application.', + 'profile_name_desc' => 'Konfigurera ditt visningsnamn som kommer att vara synligt för andra användare i systemet genom den aktivitet du utför och det innehåll du äger.', + 'profile_email_desc' => 'Denna e-postadress kommer att användas för aviseringar och beroende på aktiv systemautentisering, för systemåtkomst.', + 'profile_email_no_permission' => 'Tyvärr har du inte behörighet att ändra din e-postadress. Om du vill ändra detta behöver du be en administratör att göra det åt dig.', + 'profile_avatar_desc' => 'Välj en bild som kommer att representera dig för andra i systemet. Helst bör bilden vara kvadratisk och cirka 256px bred och hög.', + 'profile_admin_options' => 'Administratörsalternativ', + 'profile_admin_options_desc' => 'Ytterligare inställningar på administratörsnivå, till exempel för att hantera rolltilldelningar, hittar du för ditt användarkonto under "Inställningar > Användare" i applikationen.', 'delete_account' => 'Radera konto', 'delete_my_account' => 'Radera mitt konto', - 'delete_my_account_desc' => 'This will fully delete your user account from the system. You will not be able to recover this account or revert this action. Content you\'ve created, such as created pages and uploaded images, will remain.', - 'delete_my_account_warning' => 'Are you sure you want to delete your account?', + 'delete_my_account_desc' => 'Detta kommer permanent radera ditt användarkonto från systemet. Du kommer inte kunna återställa detta konto eller ångra denna åtgärd. Innehåll du har skapat, till exempel skapade sidor och uppladdade bilder, kommer att finnas kvar.', + 'delete_my_account_warning' => 'Är du säker på att du vill radera ditt konto?', ]; diff --git a/lang/sv/settings.php b/lang/sv/settings.php index bf9968270ab..2968183dd8f 100644 --- a/lang/sv/settings.php +++ b/lang/sv/settings.php @@ -16,8 +16,8 @@ 'app_customization' => 'Sidanpassning', 'app_features_security' => 'Funktioner och säkerhet', 'app_name' => 'Applikationsnamn', - 'app_name_desc' => 'Namnet visas i sidhuvdet och i eventuella mail.', - 'app_name_header' => 'Visa applikationsnamn i sidhuvudet?', + 'app_name_desc' => 'Namnet visas i sidhuvudet och i eventuella mejl.', + 'app_name_header' => 'Visa applikationsnamn i sidhuvudet', 'app_public_access' => 'Offentlig åtkomst', 'app_public_access_desc' => 'Om du aktiverar detta alternativ låter du icke inloggade besökare komma åt innehåll på din sida', 'app_public_access_desc_guest' => 'Åtkomst för icke inloggade besökare kan styras via användaren "Guest".', @@ -75,36 +75,36 @@ 'reg_confirm_restrict_domain_placeholder' => 'Ingen begränsning inställd', // Sorting Settings - 'sorting' => 'Lists & Sorting', - 'sorting_book_default' => 'Default Book Sort Rule', + 'sorting' => 'Listor och sortering', + 'sorting_book_default' => 'Standardsorteringsregel för böcker', 'sorting_book_default_desc' => 'Välj standard sorteringsregel som skall tillämpas på nya böcker. Detta påverkar inte befintliga böcker och kan åsidosättas per bok.', 'sorting_rules' => 'Sorteringsregler', - 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.', + 'sorting_rules_desc' => 'Detta är fördefinierade sorteringsåtgärder som kan tillämpas på innehåll i systemet.', 'sort_rule_assigned_to_x_books' => 'Tilldelad till :count bok|Tilldelad till :count böcker', 'sort_rule_create' => 'Skapa sorteringsregel', 'sort_rule_edit' => 'Redigera sorteringsregel', 'sort_rule_delete' => 'Ta bort sorteringsregel', - 'sort_rule_delete_desc' => 'Remove this sort rule from the system. Books using this sort will revert to manual sorting.', - 'sort_rule_delete_warn_books' => 'This sort rule is currently used on :count book(s). Are you sure you want to delete this?', - 'sort_rule_delete_warn_default' => 'This sort rule is currently used as the default for books. Are you sure you want to delete this?', + 'sort_rule_delete_desc' => 'Ta bort denna sorteringsregel från systemet. Böcker som använder denna sortering kommer att återgå till manuell sortering.', + 'sort_rule_delete_warn_books' => 'Denna sorteringsregel används för närvarande på :count bok/böcker. Är du säker på att du vill radera den?', + 'sort_rule_delete_warn_default' => 'Denna sorteringsregel används för närvarande som standard för böcker. Är du säker på att du vill radera den?', 'sort_rule_details' => 'Detaljer för sorteringsregler', - 'sort_rule_details_desc' => 'Set a name for this sort rule, which will appear in lists when users are selecting a sort.', - 'sort_rule_operations' => 'Sort Operations', - 'sort_rule_operations_desc' => 'Configure the sort actions to be performed by moving them from the list of available operations. Upon use, the operations will be applied in order, from top to bottom. Any changes made here will be applied to all assigned books upon save.', + 'sort_rule_details_desc' => 'Ange ett namn för denna sorteringsregel, vilket kommer att visas i listor när användare väljer en sortering.', + 'sort_rule_operations' => 'Sorteringsåtgärder', + 'sort_rule_operations_desc' => 'Konfigurera sorteringsåtgärderna som ska utföras genom att flytta dem från listan över tillgängliga åtgärder. Vid användning kommer åtgärderna att tillämpas i ordning, uppifrån och ner. Alla ändringar som görs här kommer att tillämpas på alla tilldelade böcker vid sparande.', 'sort_rule_available_operations' => 'Tillgängliga åtgärder', - 'sort_rule_available_operations_empty' => 'No operations remaining', - 'sort_rule_configured_operations' => 'Configured Operations', - 'sort_rule_configured_operations_empty' => 'Drag/add operations from the "Available Operations" list', - 'sort_rule_op_asc' => '(Asc)', - 'sort_rule_op_desc' => '(Desc)', + 'sort_rule_available_operations_empty' => 'Inga åtgärder kvar', + 'sort_rule_configured_operations' => 'Konfigurerade åtgärder', + 'sort_rule_configured_operations_empty' => 'Dra/lägg till åtgärder från listan "Tillgängliga åtgärder"', + 'sort_rule_op_asc' => '(Stigande)', + 'sort_rule_op_desc' => '(Fallande)', 'sort_rule_op_name' => 'Namn - Alfabetisk ordning', 'sort_rule_op_name_numeric' => 'Namn - Numerisk ordning', 'sort_rule_op_created_date' => 'Datum skapat', 'sort_rule_op_updated_date' => 'Datum uppdaterat', - 'sort_rule_op_chapters_first' => 'Chapters First', - 'sort_rule_op_chapters_last' => 'Chapters Last', - 'sorting_page_limits' => 'Per-Page Display Limits', - 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.', + 'sort_rule_op_chapters_first' => 'Kapitel först', + 'sort_rule_op_chapters_last' => 'Kapitel sist', + 'sorting_page_limits' => 'Gränser för antal objekt per sida', + 'sorting_page_limits_desc' => 'Ställ in hur många objekt som ska visas per sida i olika listor i systemet. Vanligtvis är ett lägre antal mer prestandaeffektivt, medan ett högre antal minskar behovet av att klicka sig igenom flera sidor. Det rekommenderas att använda en multipel av 6.', // Maintenance settings 'maint' => 'Underhåll', @@ -141,7 +141,7 @@ 'recycle_bin_contents_empty' => 'Papperskorgen är för närvarande tom', 'recycle_bin_empty' => 'Töm papperskorgen', 'recycle_bin_empty_confirm' => 'Detta kommer permanent att förstöra alla objekt i papperskorgen inklusive innehåll som finns i varje objekt. Är du säker du vill tömma papperskorgen?', - 'recycle_bin_destroy_confirm' => 'This action will permanently delete this item from the system, along with any child elements listed below, and you will not be able to restore this content. Are you sure you want to permanently delete this item?', + 'recycle_bin_destroy_confirm' => 'Denna åtgärd kommer att permanent radera detta objekt från systemet, tillsammans med eventuella underliggande element som listas nedan, och du kommer inte att kunna återställa detta innehåll. Är du säker på att du vill permanent radera detta objekt?', 'recycle_bin_destroy_list' => 'Objekt som ska förstöras', 'recycle_bin_restore_list' => 'Objekt som ska återställas', 'recycle_bin_restore_confirm' => 'Denna åtgärd kommer att återställa det raderade objektet, inklusive alla underordnade element, till deras ursprungliga plats. Om den ursprungliga platsen har tagits bort sedan dess, och är nu i papperskorgen, kommer det överordnade objektet också att behöva återställas.', @@ -168,11 +168,11 @@ // Role Settings 'roles' => 'Roller', 'role_user_roles' => 'Användarroller', - 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => ':count user assigned|:count users assigned', - 'roles_x_permissions_provided' => ':count permission|:count permissions', - 'roles_assigned_users' => 'Assigned Users', - 'roles_permissions_provided' => 'Provided Permissions', + 'roles_index_desc' => 'Roller används för att gruppera användare och ge deras medlemmar systembehörigheter. När en användare är medlem i flera roller läggs behörigheterna samman och användaren ärver alla rättigheter.', + 'roles_x_users_assigned' => ':count användare tilldelad|:count användare tilldelade', + 'roles_x_permissions_provided' => ':count behörighet|:count behörigheter', + 'roles_assigned_users' => 'Tilldelade användare', + 'roles_permissions_provided' => 'Tillhandahållna behörigheter', 'role_create' => 'Skapa ny roll', 'role_delete' => 'Ta bort roll', 'role_delete_confirm' => 'Rollen med namn \':roleName\' kommer att tas bort.', @@ -194,27 +194,27 @@ 'role_access_api' => 'Åtkomst till systemets API', 'role_manage_settings' => 'Hantera appinställningar', 'role_export_content' => 'Exportera innehåll', - 'role_import_content' => 'Import content', + 'role_import_content' => 'Importera innehåll', 'role_editor_change' => 'Ändra sidredigerare', - 'role_notifications' => 'Receive & manage notifications', - 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.', + 'role_notifications' => 'Ta emot och hantera aviseringar', + 'role_permission_note_users_and_roles' => 'Dessa behörigheter kommer i praktiken även att ge synlighet och sökmöjlighet för användare och roller i systemet.', 'role_asset' => 'Tillgång till innehåll', 'roles_system_warning' => 'Var medveten om att åtkomst till någon av ovanstående tre behörigheter kan tillåta en användare att ändra sina egna rättigheter eller andras rättigheter i systemet. Tilldela endast roller med dessa behörigheter till betrodda användare.', 'role_asset_desc' => 'Det här är standardinställningarna för allt innehåll i systemet. Eventuella anpassade rättigheter på böcker, kapitel och sidor skriver över dessa inställningar.', 'role_asset_admins' => 'Administratörer har automatisk tillgång till allt innehåll men dessa alternativ kan visa och dölja vissa gränssnittselement', 'role_asset_image_view_note' => 'Detta avser synlighet inom bildhanteraren. Faktisk åtkomst för uppladdade bildfiler kommer att bero på alternativ för bildlagring.', - 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.', + 'role_asset_users_note' => 'Dessa behörigheter kommer i praktiken även att ge synlighet och sökmöjlighet för användare i systemet.', 'role_all' => 'Alla', 'role_own' => 'Egna', 'role_controlled_by_asset' => 'Kontrolleras av den sida de laddas upp till', - 'role_controlled_by_page_delete' => 'Controlled by page delete permissions', + 'role_controlled_by_page_delete' => 'Styrs av behörigheter för radering av sidor', 'role_save' => 'Spara roll', 'role_users' => 'Användare med denna roll', 'role_users_none' => 'Inga användare tillhör den här rollen', // Users 'users' => 'Användare', - 'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.', + 'users_index_desc' => 'Skapa och hantera enskilda användarkonton i systemet. Användarkonton används för inloggning och för att tillskriva innehåll och aktivitet en användare. Åtkomstbehörigheter är i huvudsak rollbaserade, men användarens innehållsägarskap, bland andra faktorer, kan också påverka behörigheter och åtkomst.', 'user_profile' => 'Användarprofil', 'users_add_new' => 'Lägg till användare', 'users_search' => 'Sök användare', @@ -229,8 +229,8 @@ 'users_send_invite_text' => 'Du kan välja att skicka denna användare ett e-postmeddelande som tillåter dem att ställa in sitt eget lösenord, eller så kan du ställa in deras lösenord själv.', 'users_send_invite_option' => 'Skicka e-post med inbjudan', 'users_external_auth_id' => 'Externt ID för autentisering', - 'users_external_auth_id_desc' => 'When an external authentication system is in use (such as SAML2, OIDC or LDAP) this is the ID which links this BookStack user to the authentication system account. You can ignore this field if using the default email-based authentication.', - 'users_password_warning' => 'Only fill the below if you would like to change the password for this user.', + 'users_external_auth_id_desc' => 'När ett externt autentiseringssystem används (såsom SAML2, OIDC eller LDAP) är detta det ID som länkar detta BookStack-konto till kontot i autentiseringssystemet. Du kan bortse från detta fält om standardautentisering via e-post används.', + 'users_password_warning' => 'Dessa behörigheter kommer i praktiken även att ge synlighet och sökmöjlighet för användare i systemet.', 'users_system_public' => 'Den här användaren representerar eventuella gäster som använder systemet. Den kan inte användas för att logga in utan tilldeles automatiskt.', 'users_delete' => 'Ta bort användare', 'users_delete_named' => 'Ta bort användaren :userName', @@ -246,7 +246,7 @@ 'users_preferred_language' => 'Föredraget språk', 'users_preferred_language_desc' => 'Det här alternativet kommer att ändra det språk som används i användargränssnittet. Detta påverkar inget användarskapat innehåll.', 'users_social_accounts' => 'Anslutna konton', - 'users_social_accounts_desc' => 'View the status of the connected social accounts for this user. Social accounts can be used in addition to the primary authentication system for system access.', + 'users_social_accounts_desc' => 'Dessa behörigheter kommer i praktiken även att ge synlighet och sökmöjlighet för användare och roller i systemet.', 'users_social_accounts_info' => 'Här kan du ansluta dina andra konton för snabbare och smidigare inloggning. Om du kopplar från en tjänst här kommer de behörigheter som tidigare givits inte att tas bort - ta bort behörigheter genom att logga in på ditt konto på tjänsten i fråga.', 'users_social_connect' => 'Anslut konto', 'users_social_disconnect' => 'Koppla från konto', @@ -255,7 +255,7 @@ 'users_social_connected' => ':socialAccount har kopplats till ditt konto.', 'users_social_disconnected' => ':socialAccount har kopplats bort från ditt konto.', 'users_api_tokens' => 'API-nyckel', - 'users_api_tokens_desc' => 'Create and manage the access tokens used to authenticate with the BookStack REST API. Permissions for the API are managed via the user that the token belongs to.', + 'users_api_tokens_desc' => 'Ta emot och hantera aviseringar', 'users_api_tokens_none' => 'Inga API-tokens har skapats för den här användaren', 'users_api_tokens_create' => 'Skapa token', 'users_api_tokens_expires' => 'Förfaller', @@ -264,9 +264,9 @@ 'users_mfa_desc' => 'Konfigurera multifaktorsautentisering som ett extra skydd för ditt konto.', 'users_mfa_x_methods' => ':count metod konfigurerad|:count metoder konfigurerade', 'users_mfa_configure' => 'Konfigurera metoder', - '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?', + 'users_mfa_reset' => 'Återställ metoder för multifaktorautentisering', + 'users_mfa_reset_desc' => 'Detta kommer att återställa och rensa alla konfigurerade metoder för multifaktorautentisering för denna användare. Om multifaktorautentisering krävs av någon av deras roller kommer de att uppmanas att konfigurera nya metoder vid nästa inloggning.', + 'users_mfa_reset_confirm' => 'Är du säker på att du vill återställa multifaktorautentisering för denna användare?', // API Tokens 'user_api_token_create' => 'Skapa API-nyckel', @@ -288,8 +288,8 @@ // Webhooks 'webhooks' => 'Webhooks', - 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', + 'webhooks_index_desc' => 'Webhooks är ett sätt att skicka data till externa URLer när vissa åtgärder och händelser inträffar i systemet, vilket möjliggör händelsebaserad integration med externa plattformar såsom meddelande- eller aviseringssystem.', + 'webhooks_x_trigger_events' => ':count utlösande händelse|:count utlösande händelser', 'webhooks_create' => 'Skapa ny webhook', 'webhooks_none_created' => 'Inga webhooks har skapats än.', 'webhooks_edit' => 'Redigera webhook', @@ -317,7 +317,7 @@ // Licensing 'licenses' => 'Licenser', - 'licenses_desc' => 'This page details license information for BookStack in addition to the projects & libraries that are used within BookStack. Many projects listed may only be used in a development context.', + 'licenses_desc' => 'Denna sida beskriver licensinformation för BookStack samt de projekt och bibliotek som används inom BookStack. Många av de listade projekten används eventuellt bara i ett utvecklingssammanhang.', 'licenses_bookstack' => 'BookStack licens', 'licenses_php' => 'Licenser för PHP-bibliotek', 'licenses_js' => 'Licenser för JavaScript-bibliotek', diff --git a/lang/sv/validation.php b/lang/sv/validation.php index b0c1f7a1bb9..4f9298ba33d 100644 --- a/lang/sv/validation.php +++ b/lang/sv/validation.php @@ -105,11 +105,11 @@ 'url' => 'Formatet på :attribute är ogiltigt.', 'uploaded' => 'Filen kunde inte laddas upp. Servern kanske inte tillåter filer med denna storlek.', - 'zip_file' => 'The :attribute needs to reference a file within the ZIP.', - 'zip_file_size' => 'The file :attribute must not exceed :size MB.', - 'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.', - 'zip_model_expected' => 'Data object expected but ":type" found.', - 'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.', + 'zip_file' => ':attribute måste referera till en fil inom ZIP-filen.', + 'zip_file_size' => 'Filen :attribute får inte överstiga :size MB.', + 'zip_file_mime' => ':attribute måste referera till en fil av typen :validTypes, hittade :foundType.', + 'zip_model_expected' => 'Dataobjekt förväntades men ":type" hittades.', + 'zip_unique' => ':attribute måste referera till en fil inom ZIP-filen.', // Custom validation lines 'custom' => [ diff --git a/lang/tr/auth.php b/lang/tr/auth.php index f8942a4dfc3..907487baff9 100644 --- a/lang/tr/auth.php +++ b/lang/tr/auth.php @@ -8,7 +8,7 @@ 'failed' => 'Girdiğiniz bilgiler kayıtlarımızla uyuşmuyor.', 'throttle' => 'Çok fazla giriş yapmaya çalıştınız. Lütfen :seconds saniye içinde tekrar deneyin.', - 'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.', + 'mfa_throttle' => 'Çok fazla çok faktörlü doğrulama denemesi yapıldı. Lütfen :seconds içinde tekrar deneyin.', // Login & Register 'sign_up' => 'Kaydol', @@ -92,7 +92,7 @@ 'mfa_option_totp_title' => 'Mobil Uygulama', 'mfa_option_totp_desc' => 'Çok aşamalı kimlik doğrulamayı kullanabilmek için Google Authenticator, Authy veya Microsoft Authenticator gibi TOTP destekleyen bir mobil uygulamaya ihtiyacınız olacaktır.', 'mfa_option_backup_codes_title' => 'Yedekleme Kodları', - 'mfa_option_backup_codes_desc' => 'Generates a set of one-time-use backup codes which you\'ll enter on login to verify your identity. Make sure to store these in a safe & secure place.', + 'mfa_option_backup_codes_desc' => 'Kimliğinizi doğrulamak için giriş yaparken kullanacağınız tek kullanımlık yedek kodlar oluşturur. Bunları güvenli ve sağlam bir yerde sakladığınızdan emin olun.', 'mfa_gen_confirm_and_enable' => 'Onayla ve aktive et', 'mfa_gen_backup_codes_title' => 'Yedekleme Kodları Kurulumu', 'mfa_gen_backup_codes_desc' => 'Aşağıdaki kod listesini güvenli bir yerde sakla. Sisteme giriş yaparken kodlardan birini ikinci bir kimlik doğrulama mekanizması olarak kullanabileceksin.', diff --git a/lang/tr/common.php b/lang/tr/common.php index 5b5e753873a..14baba8b2b1 100644 --- a/lang/tr/common.php +++ b/lang/tr/common.php @@ -20,7 +20,7 @@ 'description' => 'Açıklama', 'role' => 'Rol', 'cover_image' => 'Kapak resmi', - 'cover_image_description' => 'This image should be approximately 440x250px although it will be flexibly scaled & cropped to fit the user interface in different scenarios as required, so actual dimensions for display will differ.', + 'cover_image_description' => 'Bu görüntü yaklaşık 440x250px olmalıdır, ancak kullanıcı arayüzüne farklı senaryolarda uyacak şekilde esnek ölçeklendirilip kırpılacak, bu yüzden gerçek ekran boyutları farklı olacaktır.', // Actions 'actions' => 'İşlemler', diff --git a/lang/tr/editor.php b/lang/tr/editor.php index c020c82fddf..79a82fdf61b 100644 --- a/lang/tr/editor.php +++ b/lang/tr/editor.php @@ -13,7 +13,7 @@ 'cancel' => 'İptal', 'save' => 'Kaydet', 'close' => 'Kapat', - 'apply' => 'Apply', + 'apply' => 'Uygula', 'undo' => 'Geri al', 'redo' => 'Yeniden yap', 'left' => 'Sol', From de6e6f3dd97cafbe960f29a858c7a456b704d629 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 29 Jul 2026 09:56:08 +0100 Subject: [PATCH 203/204] Deps: Updated PHP package versions --- composer.lock | 331 ++++++++++++++++++++++++-------------------------- 1 file changed, 157 insertions(+), 174 deletions(-) diff --git a/composer.lock b/composer.lock index 685d88f524d..3576e38ca2c 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.387.1", + "version": "3.389.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "2e1a16e9c87f2a069aa8a0a14a314dd148de21e1" + "reference": "784e0fb95e752e55c4654b5800b900c78f6d3990" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2e1a16e9c87f2a069aa8a0a14a314dd148de21e1", - "reference": "2e1a16e9c87f2a069aa8a0a14a314dd148de21e1", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/784e0fb95e752e55c4654b5800b900c78f6d3990", + "reference": "784e0fb95e752e55c4654b5800b900c78f6d3990", "shasum": "" }, "require": { @@ -79,9 +79,9 @@ "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", - "guzzlehttp/guzzle": "^7.4.5", - "guzzlehttp/promises": "^2.0", - "guzzlehttp/psr7": "^2.4.5", + "guzzlehttp/guzzle": "^7.8.2 || ^8.0", + "guzzlehttp/promises": "^2.0.3 || ^3.0", + "guzzlehttp/psr7": "^2.6.3 || ^3.0", "mtdowling/jmespath.php": "^2.9.1", "php": ">=8.1", "psr/http-message": "^1.0 || ^2.0", @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.387.1" + "source": "https://github.com/aws/aws-sdk-php/tree/3.389.2" }, - "time": "2026-07-01T18:10:42+00:00" + "time": "2026-07-28T18:10:25+00:00" }, { "name": "bacon/bacon-qr-code", @@ -635,16 +635,16 @@ }, { "name": "dompdf/dompdf", - "version": "v3.1.5", + "version": "v3.1.6", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496" + "reference": "6d4b4eb8500f7a786da8868ba463a71b725a4005" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", - "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/6d4b4eb8500f7a786da8868ba463a71b725a4005", + "reference": "6d4b4eb8500f7a786da8868ba463a71b725a4005", "shasum": "" }, "require": { @@ -693,9 +693,9 @@ "homepage": "https://github.com/dompdf/dompdf", "support": { "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/v3.1.5" + "source": "https://github.com/dompdf/dompdf/tree/v3.1.6" }, - "time": "2026-03-03T13:54:37+00:00" + "time": "2026-07-20T12:29:38+00:00" }, { "name": "dompdf/php-font-lib", @@ -1181,22 +1181,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.13.1", + "version": "7.15.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "55901a76dfd2006a0cc012b9e3c5b487f796478d" + "reference": "744101956d78b7c1384d0cbf379db13e859167bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/55901a76dfd2006a0cc012b9e3c5b487f796478d", - "reference": "55901a76dfd2006a0cc012b9e3c5b487f796478d", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/744101956d78b7c1384d0cbf379db13e859167bf", + "reference": "744101956d78b7c1384d0cbf379db13e859167bf", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^2.5", - "guzzlehttp/psr7": "^2.12.3", + "guzzlehttp/promises": "^2.5.1", + "guzzlehttp/psr7": "^2.13", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.5 || ^3.0", @@ -1208,8 +1208,8 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "guzzle/client-integration-tests": "3.0.2", - "guzzlehttp/test-server": "^0.6", + "guzzle/client-integration-tests": "3.0.3", + "guzzlehttp/test-server": "^0.7", "php-http/message-factory": "^1.1", "phpunit/phpunit": "^8.5.52 || ^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" @@ -1289,7 +1289,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.13.1" + "source": "https://github.com/guzzle/guzzle/tree/7.15.2" }, "funding": [ { @@ -1305,20 +1305,20 @@ "type": "tidelift" } ], - "time": "2026-06-29T20:14:18+00:00" + "time": "2026-07-26T23:23:20+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.5.0", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "4360e982f87f5f258bf872d094647791db2f4c8e" + "reference": "9ad1e4fc607446a055b95870c7f668e93b5cff29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/4360e982f87f5f258bf872d094647791db2f4c8e", - "reference": "4360e982f87f5f258bf872d094647791db2f4c8e", + "url": "https://api.github.com/repos/guzzle/promises/zipball/9ad1e4fc607446a055b95870c7f668e93b5cff29", + "reference": "9ad1e4fc607446a055b95870c7f668e93b5cff29", "shasum": "" }, "require": { @@ -1373,7 +1373,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.5.0" + "source": "https://github.com/guzzle/promises/tree/2.5.1" }, "funding": [ { @@ -1389,20 +1389,20 @@ "type": "tidelift" } ], - "time": "2026-06-02T12:23:43+00:00" + "time": "2026-07-08T15:48:39+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.12.3", + "version": "2.13.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "7ec62dc3f44aa218487dbed81a9bf9bc647be55d" + "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/7ec62dc3f44aa218487dbed81a9bf9bc647be55d", - "reference": "7ec62dc3f44aa218487dbed81a9bf9bc647be55d", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/dad89620b7a6edb60c15858442eb2e408b45d8f4", + "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4", "shasum": "" }, "require": { @@ -1492,7 +1492,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.12.3" + "source": "https://github.com/guzzle/psr7/tree/2.13.0" }, "funding": [ { @@ -1508,20 +1508,20 @@ "type": "tidelift" } ], - "time": "2026-06-23T15:21:08+00:00" + "time": "2026-07-16T22:23:49+00:00" }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.8", + "version": "v1.0.10", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "9c19128923b05a5d7355e5d2318d7808b7e33bbd" + "reference": "f6c24c21f42b990e9a58912b332d0874df6ba839" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/9c19128923b05a5d7355e5d2318d7808b7e33bbd", - "reference": "9c19128923b05a5d7355e5d2318d7808b7e33bbd", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/f6c24c21f42b990e9a58912b332d0874df6ba839", + "reference": "f6c24c21f42b990e9a58912b332d0874df6ba839", "shasum": "" }, "require": { @@ -1578,7 +1578,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.8" + "source": "https://github.com/guzzle/uri-template/tree/v1.0.10" }, "funding": [ { @@ -1594,7 +1594,7 @@ "type": "tidelift" } ], - "time": "2026-06-23T13:02:23+00:00" + "time": "2026-07-17T13:53:03+00:00" }, { "name": "intervention/gif", @@ -1809,16 +1809,16 @@ }, { "name": "laravel/framework", - "version": "v12.62.0", + "version": "v12.64.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "f7e61eb1e0e06a38996802b769bce9127aec227c" + "reference": "727a8ea2949c23ca8b5316b86a00984b6017b7a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/f7e61eb1e0e06a38996802b769bce9127aec227c", - "reference": "f7e61eb1e0e06a38996802b769bce9127aec227c", + "url": "https://api.github.com/repos/laravel/framework/zipball/727a8ea2949c23ca8b5316b86a00984b6017b7a0", + "reference": "727a8ea2949c23ca8b5316b86a00984b6017b7a0", "shasum": "" }, "require": { @@ -2027,7 +2027,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-06-09T13:50:13+00:00" + "time": "2026-07-14T14:25:37+00:00" }, { "name": "laravel/prompts", @@ -2090,16 +2090,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v2.0.13", + "version": "v2.0.15", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce" + "reference": "dccd8bcb851bb03fcc005df650b708b57cc52661" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b566ee0dd251f3c4078bed003a7ce015f5ea6dce", - "reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/dccd8bcb851bb03fcc005df650b708b57cc52661", + "reference": "dccd8bcb851bb03fcc005df650b708b57cc52661", "shasum": "" }, "require": { @@ -2147,20 +2147,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2026-04-16T14:03:50+00:00" + "time": "2026-07-21T16:49:22+00:00" }, { "name": "laravel/socialite", - "version": "v5.28.0", + "version": "v5.29.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "4c131ff4b24d8881a9c8fe4eecb5ffeff9803f26" + "reference": "cd343a5841f02292af119ee607edc71300c9ae4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/4c131ff4b24d8881a9c8fe4eecb5ffeff9803f26", - "reference": "4c131ff4b24d8881a9c8fe4eecb5ffeff9803f26", + "url": "https://api.github.com/repos/laravel/socialite/zipball/cd343a5841f02292af119ee607edc71300c9ae4f", + "reference": "cd343a5841f02292af119ee607edc71300c9ae4f", "shasum": "" }, "require": { @@ -2219,7 +2219,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2026-06-12T03:24:05+00:00" + "time": "2026-07-01T13:50:23+00:00" }, { "name": "laravel/tinker", @@ -2289,16 +2289,16 @@ }, { "name": "league/commonmark", - "version": "2.8.2", + "version": "2.8.3", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "59fb075d2101740c337c7216e3f32b36c204218b" + "reference": "1902f60f984235023acbe03db6ad614a37b3c3e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/59fb075d2101740c337c7216e3f32b36c204218b", - "reference": "59fb075d2101740c337c7216e3f32b36c204218b", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/1902f60f984235023acbe03db6ad614a37b3c3e7", + "reference": "1902f60f984235023acbe03db6ad614a37b3c3e7", "shasum": "" }, "require": { @@ -2320,8 +2320,8 @@ "github/gfm": "0.29.0", "michelf/php-markdown": "^1.4 || ^2.0", "nyholm/psr7": "^1.5", - "phpstan/phpstan": "^1.8.2", - "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "phpstan/phpstan": "^2.0.0", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0 || ^12.0.0 || ^13.0.0", "scrutinizer/ocular": "^1.8.1", "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", @@ -2392,7 +2392,7 @@ "type": "tidelift" } ], - "time": "2026-03-19T13:16:38+00:00" + "time": "2026-07-12T15:29:16+00:00" }, { "name": "league/config", @@ -2478,16 +2478,16 @@ }, { "name": "league/flysystem", - "version": "3.35.1", + "version": "3.35.2", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "f23af6c5aafd958a7593029a271d77baf5ed793c" + "reference": "b277b5dc3d56650b68904117124e79c851e12376" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f23af6c5aafd958a7593029a271d77baf5ed793c", - "reference": "f23af6c5aafd958a7593029a271d77baf5ed793c", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/b277b5dc3d56650b68904117124e79c851e12376", + "reference": "b277b5dc3d56650b68904117124e79c851e12376", "shasum": "" }, "require": { @@ -2555,22 +2555,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.35.1" + "source": "https://github.com/thephpleague/flysystem/tree/3.35.2" }, - "time": "2026-06-25T06:52:23+00:00" + "time": "2026-07-06T14:42:07+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.35.1", + "version": "3.35.2", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "3f93d3f4bd5c12a5dfb34a7267c40b1bc4587b94" + "reference": "8475ef9adfc6498b85469e2abec6fe3118cd08c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/3f93d3f4bd5c12a5dfb34a7267c40b1bc4587b94", - "reference": "3f93d3f4bd5c12a5dfb34a7267c40b1bc4587b94", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/8475ef9adfc6498b85469e2abec6fe3118cd08c4", + "reference": "8475ef9adfc6498b85469e2abec6fe3118cd08c4", "shasum": "" }, "require": { @@ -2610,9 +2610,9 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.35.1" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.35.2" }, - "time": "2026-06-25T06:51:08+00:00" + "time": "2026-07-01T23:25:49+00:00" }, { "name": "league/flysystem-local", @@ -2754,16 +2754,16 @@ }, { "name": "league/mime-type-detection", - "version": "1.16.0", + "version": "1.17.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/f5f47eff7c48ed1003069a2ca67f316fb4021c76", + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76", "shasum": "" }, "require": { @@ -2773,7 +2773,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0 || ^11.0 || ^12.0" }, "type": "library", "autoload": { @@ -2794,7 +2794,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.17.0" }, "funding": [ { @@ -2806,7 +2806,7 @@ "type": "tidelift" } ], - "time": "2024-09-21T08:32:55+00:00" + "time": "2026-07-09T11:49:27+00:00" }, { "name": "league/oauth1-client", @@ -3303,16 +3303,16 @@ }, { "name": "mtdowling/jmespath.php", - "version": "2.9.1", + "version": "2.9.2", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "9c208ba27ae7d90853c288b3795d6702eb251d34" + "reference": "2157c5e50e813ec6a96c1eed3be7f64a20fb32a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9c208ba27ae7d90853c288b3795d6702eb251d34", - "reference": "9c208ba27ae7d90853c288b3795d6702eb251d34", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/2157c5e50e813ec6a96c1eed3be7f64a20fb32a8", + "reference": "2157c5e50e813ec6a96c1eed3be7f64a20fb32a8", "shasum": "" }, "require": { @@ -3363,22 +3363,22 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.9.1" + "source": "https://github.com/jmespath/jmespath.php/tree/2.9.2" }, - "time": "2026-06-11T10:43:56+00:00" + "time": "2026-07-06T18:56:19+00:00" }, { "name": "nesbot/carbon", - "version": "3.13.0", + "version": "3.13.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "40f6618f052df16b545f626fbf9a878e6497d16a" + "reference": "2937ad3d1d2c506fd2bc97d571438a95641f44e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/40f6618f052df16b545f626fbf9a878e6497d16a", - "reference": "40f6618f052df16b545f626fbf9a878e6497d16a", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/2937ad3d1d2c506fd2bc97d571438a95641f44e2", + "reference": "2937ad3d1d2c506fd2bc97d571438a95641f44e2", "shasum": "" }, "require": { @@ -3470,7 +3470,7 @@ "type": "tidelift" } ], - "time": "2026-06-18T13:49:15+00:00" + "time": "2026-07-09T18:23:49+00:00" }, { "name": "nette/schema", @@ -3541,16 +3541,16 @@ }, { "name": "nette/utils", - "version": "v4.1.4", + "version": "v4.1.5", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7" + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/7da6c396d7ebe142bc857c20479d5e70a5e1aac7", - "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7", + "url": "https://api.github.com/repos/nette/utils/zipball/b043439dbdf954e6c28b5ea7e34b0100f83165e0", + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0", "shasum": "" }, "require": { @@ -3570,7 +3570,7 @@ }, "suggest": { "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-iconv": "to use Strings::chr(), ord() and reverse()", "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", "ext-json": "to use Nette\\Utils\\Json", "ext-mbstring": "to use Strings::lower() etc...", @@ -3626,26 +3626,25 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.4" + "source": "https://github.com/nette/utils/tree/v4.1.5" }, - "time": "2026-05-11T20:49:54+00:00" + "time": "2026-07-17T23:02:45+00:00" }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.8.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" @@ -3684,9 +3683,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2026-07-04T14:30:18+00:00" }, { "name": "nunomaduro/termwind", @@ -6597,16 +6596,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.38.1", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603" + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", "shasum": "" }, "require": { @@ -6655,7 +6654,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" }, "funding": [ { @@ -6675,7 +6674,7 @@ "type": "tidelift" } ], - "time": "2026-05-26T05:58:03+00:00" + "time": "2026-07-28T08:25:59+00:00" }, { "name": "symfony/polyfill-intl-idn", @@ -7020,16 +7019,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.38.2", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8" + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8", - "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6", "shasum": "" }, "require": { @@ -7076,7 +7075,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.41.0" }, "funding": [ { @@ -7096,7 +7095,7 @@ "type": "tidelift" } ], - "time": "2026-05-27T06:51:48+00:00" + "time": "2026-07-01T12:47:55+00:00" }, { "name": "symfony/polyfill-php84", @@ -7180,16 +7179,16 @@ }, { "name": "symfony/polyfill-php85", - "version": "v1.38.1", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", "shasum": "" }, "require": { @@ -7236,7 +7235,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" }, "funding": [ { @@ -7256,7 +7255,7 @@ "type": "tidelift" } ], - "time": "2026-05-26T02:25:22+00:00" + "time": "2026-07-01T12:47:55+00:00" }, { "name": "symfony/polyfill-uuid", @@ -8216,16 +8215,16 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.3", + "version": "v5.6.4", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "955e7815d677a3eaa7075231212f2110983adecc" + "reference": "416df702837983f8d5ff48c9c3fee4f5f57b980b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", - "reference": "955e7815d677a3eaa7075231212f2110983adecc", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/416df702837983f8d5ff48c9c3fee4f5f57b980b", + "reference": "416df702837983f8d5ff48c9c3fee4f5f57b980b", "shasum": "" }, "require": { @@ -8284,7 +8283,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.4" }, "funding": [ { @@ -8296,7 +8295,7 @@ "type": "tidelift" } ], - "time": "2025-12-27T19:49:13+00:00" + "time": "2026-07-06T19:11:50+00:00" }, { "name": "voku/portable-ascii", @@ -8971,23 +8970,23 @@ }, { "name": "nunomaduro/collision", - "version": "v8.9.4", + "version": "v8.9.5", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "716af8f95a470e9094cfca09ed897b023be191a5" + "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/716af8f95a470e9094cfca09ed897b023be191a5", - "reference": "716af8f95a470e9094cfca09ed897b023be191a5", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/fb53eacd509a1d303858e2d20cfebf2d630254ec", + "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec", "shasum": "" }, "require": { "filp/whoops": "^2.18.4", "nunomaduro/termwind": "^2.4.0", "php": "^8.2.0", - "symfony/console": "^7.4.8 || ^8.0.8" + "symfony/console": "^7.4.14 || ^8.1.1" }, "conflict": { "laravel/framework": "<11.48.0 || >=14.0.0", @@ -8995,12 +8994,12 @@ }, "require-dev": { "brianium/paratest": "^7.8.5", - "larastan/larastan": "^3.9.6", - "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.5.0", - "laravel/pint": "^1.29.1", - "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.2.1", - "pestphp/pest": "^3.8.5 || ^4.4.3 || ^5.0.0", - "sebastian/environment": "^7.2.1 || ^8.0.4 || ^9.3.0" + "larastan/larastan": "^3.10.0", + "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.20.0", + "laravel/pint": "^1.29.3", + "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.3.5", + "pestphp/pest": "^3.8.5 || ^4.7.5 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.1.2 || ^9.3.2" }, "type": "library", "extra": { @@ -9063,7 +9062,7 @@ "type": "patreon" } ], - "time": "2026-04-21T14:04:20+00:00" + "time": "2026-07-15T19:09:14+00:00" }, { "name": "phar-io/manifest", @@ -9185,11 +9184,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.2.3", + "version": "2.2.6", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/4048833dd47b377287818841877fb3087289509c", - "reference": "4048833dd47b377287818841877fb3087289509c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a6e9b5a9420f6109c091e87d82683bd1a80b87ed", + "reference": "a6e9b5a9420f6109c091e87d82683bd1a80b87ed", "shasum": "" }, "require": { @@ -9245,7 +9244,7 @@ "type": "github" } ], - "time": "2026-06-30T21:15:26+00:00" + "time": "2026-07-26T21:22:49+00:00" }, { "name": "phpunit/php-code-coverage", @@ -9596,24 +9595,24 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.55", + "version": "11.5.56", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", - "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f83edffa6967c3db468d48a695ec7bcb02e9256", + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256", "shasum": "" }, "require": { "ext-dom": "*", + "ext-filter": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", - "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", @@ -9678,31 +9677,15 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.56" }, "funding": [ { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" + "url": "https://phpunit.de/sponsoring.html", + "type": "other" } ], - "time": "2026-02-18T12:37:06+00:00" + "time": "2026-07-06T14:52:39+00:00" }, { "name": "sebastian/cli-parser", From c813c1b3628c0b6bd757c12cadaa56f50724117d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 30 Jul 2026 02:42:46 +0100 Subject: [PATCH 204/204] Meta: Updated translator list --- .github/translators.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/translators.txt b/.github/translators.txt index a2f6a957066..a6e01d0ed8f 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -548,3 +548,7 @@ brtbr :: German; German Informal Ricardo Covelo (covelo12) :: Portuguese Bojan Maksimovic (PolarniMeda) :: Serbian (Cyrillic) Dian Prawira (wiradian84) :: Indonesian +Tim (timakai) :: Dutch; German Informal; French; Romanian; Catalan; Czech; Danish; German; Finnish; Hungarian; Italian; Japanese; Korean; Polish; Russian; Ukrainian; Chinese Simplified; Chinese Traditional; Portuguese, Brazilian; Persian; Spanish, Argentina; Croatian; Norwegian Nynorsk; Estonian; Uzbek; Norwegian Bokmal +dadda123 :: Swedish +Julien Muggli (JulienMuggli) :: French +nomoreshow :: Turkish