From 709533c1fb6419bc64fca04d12a3c0891e2b31ae Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 24 Nov 2021 18:58:46 +0000 Subject: [PATCH 1/7] Fixed up logical theme docs a tad - Added link to video guide on YouTube. - Formalised the customCommand docs parts I hastily added before. --- dev/docs/logical-theme-system.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/dev/docs/logical-theme-system.md b/dev/docs/logical-theme-system.md index 4d6ed719b..139055b3d 100644 --- a/dev/docs/logical-theme-system.md +++ b/dev/docs/logical-theme-system.md @@ -6,6 +6,8 @@ WARNING: This system is currently in alpha so may incur changes. Once we've gath ## Getting Started +*[Video Guide](https://www.youtube.com/watch?v=YVbpm_35crQ)* + This makes use of the theme system. Create a folder for your theme within your BookStack `themes` directory. As an example we'll use `my_theme`, so we'd create a `themes/my_theme` folder. You'll need to tell BookStack to use your theme via the `APP_THEME` option in your `.env` file. For example: `APP_THEME=my_theme`. @@ -50,6 +52,23 @@ This method allows you to register a custom social authentication driver within *See "Custom Socialite Service Example" below.* +### `Theme::registerCommand` + +This method allows you to register a custom command which can then be used via the artisan console. + +**Arguments** +- string $driverName +- array $config +- string $socialiteHandler + +**Example** + +*See "Custom Command Registration Example" below for a more detailed example.* + +```php +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). @@ -77,9 +96,10 @@ Theme::listen(ThemeEvents::APP_BOOT, function($app) { }); ``` -## Custom Commands +## Custom Command Registration Example -The logical theme system supports adding custom [artisan commands](https://laravel.com/docs/8.x/artisan) to BookStack. These can be registered in your `functions.php` file by calling `Theme::registerCommand($command)`, where `$command` is an instance of `\Symfony\Component\Console\Command\Command`. +The logical theme system supports adding custom [artisan commands](https://laravel.com/docs/8.x/artisan) to BookStack. +These can be registered in your `functions.php` file by calling `Theme::registerCommand($command)`, where `$command` is an instance of `\Symfony\Component\Console\Command\Command`. Below is an example of registering a command that could then be ran using `php artisan bookstack:meow` on the command line. From 2c21850da728dce55cec2e84ec73ed474ba0bd0a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 25 Nov 2021 15:12:32 +0000 Subject: [PATCH 2/7] Added conversion of iframes to anchors on PDF export - Replaced iframe elements with anchor elements wrapped in a paragraph. - Extracted PDF generation action to seperate class for easier mocking within testing. - Added test to cover. For #3077 --- app/Entities/Tools/ExportFormatter.php | 49 ++++++++++++++++++++------ app/Entities/Tools/PdfGenerator.php | 28 +++++++++++++++ tests/Entity/ExportTest.php | 19 ++++++++++ 3 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 app/Entities/Tools/PdfGenerator.php diff --git a/app/Entities/Tools/ExportFormatter.php b/app/Entities/Tools/ExportFormatter.php index 05d0ff134..ebe0020e7 100644 --- a/app/Entities/Tools/ExportFormatter.php +++ b/app/Entities/Tools/ExportFormatter.php @@ -7,21 +7,24 @@ use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; use BookStack\Entities\Tools\Markdown\HtmlToMarkdown; use BookStack\Uploads\ImageService; -use DomPDF; +use DOMDocument; +use DOMElement; +use DOMXPath; use Exception; -use SnappyPDF; use Throwable; class ExportFormatter { protected $imageService; + protected $pdfGenerator; /** * ExportService constructor. */ - public function __construct(ImageService $imageService) + public function __construct(ImageService $imageService, PdfGenerator $pdfGenerator) { $this->imageService = $imageService; + $this->pdfGenerator = $pdfGenerator; } /** @@ -139,16 +142,40 @@ class ExportFormatter */ protected function htmlToPdf(string $html): string { - $containedHtml = $this->containHtml($html); - $useWKHTML = config('snappy.pdf.binary') !== false && config('app.allow_untrusted_server_fetching') === true; - if ($useWKHTML) { - $pdf = SnappyPDF::loadHTML($containedHtml); - $pdf->setOption('print-media-type', true); - } else { - $pdf = DomPDF::loadHTML($containedHtml); + $html = $this->containHtml($html); + $html = $this->replaceIframesWithLinks($html); + return $this->pdfGenerator->fromHtml($html); + } + + /** + * Within the given HTML content, replace any iframe elements + * with anchor links within paragraph blocks. + */ + protected function replaceIframesWithLinks(string $html): string + { + libxml_use_internal_errors(true); + + $doc = new DOMDocument(); + $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + $xPath = new DOMXPath($doc); + + + $iframes = $xPath->query('//iframe'); + /** @var DOMElement $iframe */ + foreach ($iframes as $iframe) { + $link = $iframe->getAttribute('src'); + if (strpos($link, '//') === 0) { + $link = 'https:' . $link; + } + + $anchor = $doc->createElement('a', $link); + $anchor->setAttribute('href', $link); + $paragraph = $doc->createElement('p'); + $paragraph->appendChild($anchor); + $iframe->replaceWith($paragraph); } - return $pdf->output(); + return $doc->saveHTML(); } /** diff --git a/app/Entities/Tools/PdfGenerator.php b/app/Entities/Tools/PdfGenerator.php new file mode 100644 index 000000000..d606617a4 --- /dev/null +++ b/app/Entities/Tools/PdfGenerator.php @@ -0,0 +1,28 @@ +setOption('print-media-type', true); + } else { + $pdf = DomPDF::loadHTML($html); + } + + return $pdf->output(); + } + +} \ No newline at end of file diff --git a/tests/Entity/ExportTest.php b/tests/Entity/ExportTest.php index 9ea336db8..9a824a3da 100644 --- a/tests/Entity/ExportTest.php +++ b/tests/Entity/ExportTest.php @@ -6,6 +6,7 @@ use BookStack\Auth\Role; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; +use BookStack\Entities\Tools\PdfGenerator; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Tests\TestCase; @@ -289,6 +290,24 @@ class ExportTest extends TestCase $resp->assertDontSee('ExportWizardTheFifth'); } + public function test_page_pdf_export_converts_iframes_to_links() + { + $page = Page::query()->first()->forceFill([ + 'html' => '', + ]); + $page->save(); + + $pdfHtml = ''; + $mockPdfGenerator = $this->mock(PdfGenerator::class); + $mockPdfGenerator->shouldReceive('fromHtml') + ->with(\Mockery::capture($pdfHtml)) + ->andReturn(''); + + $this->asEditor()->get($page->getUrl('/export/pdf')); + $this->assertStringNotContainsString('iframe>', $pdfHtml); + $this->assertStringContainsString('

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

', $pdfHtml); + } + public function test_page_markdown_export() { $page = Page::query()->first(); From 42703dd859f6eb1917c2547da106f0de646674a6 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 28 Nov 2021 21:01:35 +0000 Subject: [PATCH 3/7] Tweaked pdf export iframe replacement to fix compatibility Was using a method that wasn't a proper available part of the DomElement API. --- app/Entities/Tools/ExportFormatter.php | 4 ++-- app/Entities/Tools/PdfGenerator.php | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/Entities/Tools/ExportFormatter.php b/app/Entities/Tools/ExportFormatter.php index ebe0020e7..7f377cadb 100644 --- a/app/Entities/Tools/ExportFormatter.php +++ b/app/Entities/Tools/ExportFormatter.php @@ -144,6 +144,7 @@ class ExportFormatter { $html = $this->containHtml($html); $html = $this->replaceIframesWithLinks($html); + return $this->pdfGenerator->fromHtml($html); } @@ -159,7 +160,6 @@ class ExportFormatter $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); $xPath = new DOMXPath($doc); - $iframes = $xPath->query('//iframe'); /** @var DOMElement $iframe */ foreach ($iframes as $iframe) { @@ -172,7 +172,7 @@ class ExportFormatter $anchor->setAttribute('href', $link); $paragraph = $doc->createElement('p'); $paragraph->appendChild($anchor); - $iframe->replaceWith($paragraph); + $iframe->parentNode->replaceChild($paragraph, $iframe); } return $doc->saveHTML(); diff --git a/app/Entities/Tools/PdfGenerator.php b/app/Entities/Tools/PdfGenerator.php index d606617a4..a14f29d4b 100644 --- a/app/Entities/Tools/PdfGenerator.php +++ b/app/Entities/Tools/PdfGenerator.php @@ -2,12 +2,11 @@ namespace BookStack\Entities\Tools; -use Barryvdh\Snappy\Facades\SnappyPdf; use Barryvdh\DomPDF\Facade as DomPDF; +use Barryvdh\Snappy\Facades\SnappyPdf; class PdfGenerator { - /** * Generate PDF content from the given HTML content. */ @@ -24,5 +23,4 @@ class PdfGenerator return $pdf->output(); } - -} \ No newline at end of file +} From b4fa82e3298a15443ca40bff205b7a16a1031d92 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 30 Nov 2021 00:06:17 +0000 Subject: [PATCH 4/7] Fixed related permissions query not considering drafts Page-related items added on drafts could be visible in certain scenarios since the applied permissions query filters would not consider page draft visibility. This commit alters queries on related items to apply such filtering. Included test to cover API scenario. Thanks to @haxatron for reporting. --- app/Actions/ActivityService.php | 2 +- app/Auth/Permissions/PermissionService.php | 84 ++++++++++++++-------- app/Exceptions/Handler.php | 10 ++- tests/Api/AttachmentsApiTest.php | 23 ++++++ 4 files changed, 86 insertions(+), 33 deletions(-) diff --git a/app/Actions/ActivityService.php b/app/Actions/ActivityService.php index 983c1a603..73dc76de0 100644 --- a/app/Actions/ActivityService.php +++ b/app/Actions/ActivityService.php @@ -133,7 +133,7 @@ class ActivityService } /** - * Get latest activity for a user, Filtering out similar items. + * Get the latest activity for a user, Filtering out similar items. */ public function userActivity(User $user, int $count = 20, int $page = 0): array { diff --git a/app/Auth/Permissions/PermissionService.php b/app/Auth/Permissions/PermissionService.php index 139725339..4214861c2 100644 --- a/app/Auth/Permissions/PermissionService.php +++ b/app/Auth/Permissions/PermissionService.php @@ -602,25 +602,35 @@ class PermissionService /** * Filter items that have entities set as a polymorphic relation. + * For simplicity, this will not return results attached to draft pages. + * Draft pages should never really have related items though. * * @param Builder|QueryBuilder $query */ public function filterRestrictedEntityRelations($query, string $tableName, string $entityIdColumn, string $entityTypeColumn, string $action = 'view') { $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn]; + $pageMorphClass = (new Page())->getMorphClass(); - $q = $query->where(function ($query) use ($tableDetails, $action) { - $query->whereExists(function ($permissionQuery) use (&$tableDetails, $action) { - /** @var Builder $permissionQuery */ - $permissionQuery->select(['role_id'])->from('joint_permissions') - ->whereColumn('joint_permissions.entity_id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) - ->whereColumn('joint_permissions.entity_type', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn']) - ->where('action', '=', $action) - ->whereIn('role_id', $this->getCurrentUserRoles()) - ->where(function (QueryBuilder $query) { - $this->addJointHasPermissionCheck($query, $this->currentUser()->id); - }); - }); + $q = $query->whereExists(function ($permissionQuery) use (&$tableDetails, $action) { + /** @var Builder $permissionQuery */ + $permissionQuery->select(['role_id'])->from('joint_permissions') + ->whereColumn('joint_permissions.entity_id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) + ->whereColumn('joint_permissions.entity_type', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn']) + ->where('joint_permissions.action', '=', $action) + ->whereIn('joint_permissions.role_id', $this->getCurrentUserRoles()) + ->where(function (QueryBuilder $query) { + $this->addJointHasPermissionCheck($query, $this->currentUser()->id); + }); + })->where(function ($query) use ($tableDetails, $pageMorphClass) { + /** @var Builder $query */ + $query->where($tableDetails['entityTypeColumn'], '!=', $pageMorphClass) + ->orWhereExists(function(QueryBuilder $query) use ($tableDetails, $pageMorphClass) { + $query->select('id')->from('pages') + ->whereColumn('pages.id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) + ->where($tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'], '=', $pageMorphClass) + ->where('pages.draft', '=', false); + }); }); $this->clean(); @@ -634,25 +644,39 @@ class PermissionService */ public function filterRelatedEntity(string $entityClass, Builder $query, string $tableName, string $entityIdColumn): Builder { - $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn]; - $morphClass = app($entityClass)->getMorphClass(); + $fullEntityIdColumn = $tableName . '.' . $entityIdColumn; + $instance = new $entityClass; + $morphClass = $instance->getMorphClass(); - $q = $query->where(function ($query) use ($tableDetails, $morphClass) { - $query->where(function ($query) use (&$tableDetails, $morphClass) { - $query->whereExists(function ($permissionQuery) use (&$tableDetails, $morphClass) { - /** @var Builder $permissionQuery */ - $permissionQuery->select('id')->from('joint_permissions') - ->whereColumn('joint_permissions.entity_id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) - ->where('entity_type', '=', $morphClass) - ->where('action', '=', 'view') - ->whereIn('role_id', $this->getCurrentUserRoles()) - ->where(function (QueryBuilder $query) { - $this->addJointHasPermissionCheck($query, $this->currentUser()->id); - }); + $existsQuery = function($permissionQuery) use ($fullEntityIdColumn, $morphClass) { + /** @var Builder $permissionQuery */ + $permissionQuery->select('joint_permissions.role_id')->from('joint_permissions') + ->whereColumn('joint_permissions.entity_id', '=', $fullEntityIdColumn) + ->where('joint_permissions.entity_type', '=', $morphClass) + ->where('joint_permissions.action', '=', 'view') + ->whereIn('joint_permissions.role_id', $this->getCurrentUserRoles()) + ->where(function (QueryBuilder $query) { + $this->addJointHasPermissionCheck($query, $this->currentUser()->id); }); - })->orWhere($tableDetails['entityIdColumn'], '=', 0); + }; + + $q = $query->where(function ($query) use ($existsQuery, $fullEntityIdColumn) { + $query->whereExists($existsQuery) + ->orWhere($fullEntityIdColumn, '=', 0); }); + if ($instance instanceof Page) { + // Prevent visibility of non-owned draft pages + $q->whereExists(function(QueryBuilder $query) use ($fullEntityIdColumn) { + $query->select('id')->from('pages') + ->whereColumn('pages.id', '=', $fullEntityIdColumn) + ->where(function (QueryBuilder $query) { + $query->where('pages.draft', '=', false) + ->orWhere('pages.owned_by', '=', $this->currentUser()->id); + }); + }); + } + $this->clean(); return $q; @@ -666,9 +690,9 @@ class PermissionService */ protected function addJointHasPermissionCheck($query, int $userIdToCheck) { - $query->where('has_permission', '=', true)->orWhere(function ($query) use ($userIdToCheck) { - $query->where('has_permission_own', '=', true) - ->where('owned_by', '=', $userIdToCheck); + $query->where('joint_permissions.has_permission', '=', true)->orWhere(function ($query) use ($userIdToCheck) { + $query->where('joint_permissions.has_permission_own', '=', true) + ->where('joint_permissions.owned_by', '=', $userIdToCheck); }); } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 3b4ad4a4d..7ec502525 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -4,6 +4,7 @@ namespace BookStack\Exceptions; use Exception; use Illuminate\Auth\AuthenticationException; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -75,15 +76,20 @@ class Handler extends ExceptionHandler /** * Render an exception when the API is in use. */ - protected function renderApiException(Exception $e): JsonResponse + protected function renderApiException(Throwable $e): JsonResponse { - $code = $e->getCode() === 0 ? 500 : $e->getCode(); + $code = 500; $headers = []; + if ($e instanceof HttpException) { $code = $e->getStatusCode(); $headers = $e->getHeaders(); } + if ($e instanceof ModelNotFoundException) { + $code = 404; + } + $responseData = [ 'error' => [ 'message' => $e->getMessage(), diff --git a/tests/Api/AttachmentsApiTest.php b/tests/Api/AttachmentsApiTest.php index ceab5d49a..bfa47343e 100644 --- a/tests/Api/AttachmentsApiTest.php +++ b/tests/Api/AttachmentsApiTest.php @@ -224,6 +224,29 @@ class AttachmentsApiTest extends TestCase unlink(storage_path($attachment->path)); } + public function test_attachment_not_visible_on_other_users_draft() + { + $this->actingAsApiAdmin(); + $editor = $this->getEditor(); + + /** @var Page $page */ + $page = Page::query()->first(); + $page->draft = true; + $page->owned_by = $editor; + $page->save(); + $this->regenEntityPermissions($page); + + $attachment = $this->createAttachmentForPage($page, [ + 'name' => 'my attachment', + 'path' => 'https://example.com', + 'order' => 1, + ]); + + $resp = $this->getJson("{$this->baseEndpoint}/{$attachment->id}"); + + $resp->assertStatus(404); + } + public function test_update_endpoint() { $this->actingAsApiAdmin(); From 3b3eb0f44fa96aecac58759f31da2f94bde64c5e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 30 Nov 2021 13:55:56 +0000 Subject: [PATCH 5/7] Updated API session auth to consider public access setting For #3091 --- app/Http/Middleware/ApiAuthenticate.php | 11 ++++++++++- tests/Api/ApiAuthTest.php | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/app/Http/Middleware/ApiAuthenticate.php b/app/Http/Middleware/ApiAuthenticate.php index bc584d3c5..508efa028 100644 --- a/app/Http/Middleware/ApiAuthenticate.php +++ b/app/Http/Middleware/ApiAuthenticate.php @@ -35,7 +35,7 @@ class ApiAuthenticate // 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. if (signedInUser() || session()->isStarted()) { - if (!user()->can('access-api')) { + if (!$this->sessionUserHasApiAccess()) { throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403); } @@ -49,6 +49,15 @@ class ApiAuthenticate auth()->authenticate(); } + /** + * Check if the active session user has API access + */ + protected function sessionUserHasApiAccess(): bool + { + $hasApiPermission = user()->can('access-api'); + return $hasApiPermission && hasAppAccess(); + } + /** * Provide a standard API unauthorised response. */ diff --git a/tests/Api/ApiAuthTest.php b/tests/Api/ApiAuthTest.php index c45bd77ee..cc6818e27 100644 --- a/tests/Api/ApiAuthTest.php +++ b/tests/Api/ApiAuthTest.php @@ -3,6 +3,7 @@ namespace Tests\Api; use BookStack\Auth\Permissions\RolePermission; +use BookStack\Auth\Role; use BookStack\Auth\User; use Carbon\Carbon; use Tests\TestCase; @@ -91,6 +92,26 @@ class ApiAuthTest extends TestCase $resp->assertJson($this->errorResponse('The owner of the used API token does not have permission to make API calls', 403)); } + public function test_access_prevented_for_guest_users_with_api_permission_while_public_access_disabled() + { + $this->disableCookieEncryption(); + $publicRole = Role::getSystemRole('public'); + $accessApiPermission = RolePermission::getByName('access-api'); + $publicRole->attachPermission($accessApiPermission); + + $this->withCookie('bookstack_session', 'abc123'); + + // Test API access when not public + setting()->put('app-public', false); + $resp = $this->get($this->endpoint); + $resp->assertStatus(403); + + // Test API access when public + setting()->put('app-public', true); + $resp = $this->get($this->endpoint); + $resp->assertStatus(200); + } + public function test_token_expiry_checked() { $editor = $this->getEditor(); From 3e97fdf82783131082f5add32d81bc5aa6a188df Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 30 Nov 2021 14:24:35 +0000 Subject: [PATCH 6/7] New Crowdin updates (#3076) * New translations entities.php (Chinese Simplified) * New translations settings.php (Portuguese, Brazilian) * New translations validation.php (Portuguese, Brazilian) * New translations common.php (Chinese Simplified) * New translations settings.php (Chinese Simplified) * New translations auth.php (Turkish) --- resources/lang/pt_BR/settings.php | 2 +- resources/lang/pt_BR/validation.php | 4 ++-- resources/lang/tr/auth.php | 10 +++++----- resources/lang/zh_CN/common.php | 4 ++-- resources/lang/zh_CN/entities.php | 2 +- resources/lang/zh_CN/settings.php | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/resources/lang/pt_BR/settings.php b/resources/lang/pt_BR/settings.php index 4264e22c0..9f829e6db 100644 --- a/resources/lang/pt_BR/settings.php +++ b/resources/lang/pt_BR/settings.php @@ -38,7 +38,7 @@ return [ '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_select' => 'Selecione uma página', 'app_footer_links' => 'Links do Rodapé', - 'app_footer_links_desc' => 'Add links to show within the site footer. These will be displayed at the bottom of most pages, including those that do not require login. You can use a label of "trans::" to use system-defined translations. For example: Using "trans::common.privacy_policy" will provide the translated text "Privacy Policy" and "trans::common.terms_of_service" will provide the translated text "Terms of Service".', + '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".', 'app_footer_links_label' => 'Etiqueta do Link', 'app_footer_links_url' => 'URL do Link', 'app_footer_links_add' => 'Adicionar Link de Rodapé', diff --git a/resources/lang/pt_BR/validation.php b/resources/lang/pt_BR/validation.php index 4bf85d7cf..705534cb6 100644 --- a/resources/lang/pt_BR/validation.php +++ b/resources/lang/pt_BR/validation.php @@ -15,7 +15,7 @@ return [ 'alpha_dash' => 'O campo :attribute deve conter apenas letras, números, traços e underlines.', 'alpha_num' => 'O campo :attribute deve conter apenas letras e números.', 'array' => 'O campo :attribute deve ser uma array.', - 'backup_codes' => 'The provided code is not valid or has already been used.', + 'backup_codes' => 'O código fornecido não é válido ou já foi usado.', 'before' => 'O campo :attribute deve ser uma data anterior à data :date.', 'between' => [ 'numeric' => 'O campo :attribute deve estar entre :min e :max.', @@ -99,7 +99,7 @@ return [ ], 'string' => 'O campo :attribute deve ser uma string.', 'timezone' => 'O campo :attribute deve conter uma timezone válida.', - 'totp' => 'The provided code is not valid or has expired.', + 'totp' => 'O código fornecido não é válido ou expirou.', 'unique' => 'Já existe um campo/dado de nome :attribute.', '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.', diff --git a/resources/lang/tr/auth.php b/resources/lang/tr/auth.php index 8d863fdea..ce9ded13a 100644 --- a/resources/lang/tr/auth.php +++ b/resources/lang/tr/auth.php @@ -76,12 +76,12 @@ return [ // Multi-factor Authentication 'mfa_setup' => 'Setup Multi-Factor Authentication', 'mfa_setup_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.', - 'mfa_setup_configured' => 'Already configured', - 'mfa_setup_reconfigure' => 'Reconfigure', - 'mfa_setup_remove_confirmation' => 'Are you sure you want to remove this multi-factor authentication method?', + 'mfa_setup_configured' => 'Zaten yapılandırıldı', + 'mfa_setup_reconfigure' => 'Yeniden yapılandır', + 'mfa_setup_remove_confirmation' => '2 adımlı doğrulamayı kaldırmak istediğinize emin misiniz?', 'mfa_setup_action' => 'Setup', 'mfa_backup_codes_usage_limit_warning' => '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.', - 'mfa_option_totp_title' => 'Mobile App', + 'mfa_option_totp_title' => 'Mobil Uygulama', 'mfa_option_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.', 'mfa_option_backup_codes_title' => 'Backup Codes', 'mfa_option_backup_codes_desc' => 'Securely store a set of one-time-use backup codes which you can enter to verify your identity.', @@ -106,5 +106,5 @@ return [ 'mfa_verify_backup_code_desc' => 'Enter one of your remaining backup codes below:', 'mfa_verify_backup_code_enter_here' => 'Enter backup code here', 'mfa_verify_totp_desc' => 'Enter the code, generated using your mobile app, below:', - 'mfa_setup_login_notification' => 'Multi-factor method configured, Please now login again using the configured method.', + 'mfa_setup_login_notification' => '2 adımlı doğrulama ayarlandı, Lütfen 2 adımlı doğrulama kullanarak yeniden giriş yapınız.', ]; diff --git a/resources/lang/zh_CN/common.php b/resources/lang/zh_CN/common.php index 1201178f0..f1bd1f172 100644 --- a/resources/lang/zh_CN/common.php +++ b/resources/lang/zh_CN/common.php @@ -75,8 +75,8 @@ return [ // Header 'header_menu_expand' => '展开标头菜单', 'profile_menu' => '个人资料', - 'view_profile' => '查看资料', - 'edit_profile' => '编辑资料', + 'view_profile' => '查看个人资料', + 'edit_profile' => '编辑个人资料', 'dark_mode' => '夜间模式', 'light_mode' => '日间模式', diff --git a/resources/lang/zh_CN/entities.php b/resources/lang/zh_CN/entities.php index cce5a2ecf..dd877441a 100644 --- a/resources/lang/zh_CN/entities.php +++ b/resources/lang/zh_CN/entities.php @@ -263,7 +263,7 @@ return [ 'tags_assigned_chapters' => '有这个标签的章节', 'tags_assigned_books' => '有这个标签的图书', 'tags_assigned_shelves' => '有这个标签的书架', - 'tags_x_unique_values' => '个不重复项目', + 'tags_x_unique_values' => ':count 个不重复项目', 'tags_all_values' => '所有值', 'tags_view_tags' => '查看标签', 'tags_view_existing_tags' => '查看已有的标签', diff --git a/resources/lang/zh_CN/settings.php b/resources/lang/zh_CN/settings.php index b3a28a73a..ee6f6439a 100755 --- a/resources/lang/zh_CN/settings.php +++ b/resources/lang/zh_CN/settings.php @@ -151,7 +151,7 @@ return [ 'role_manage_settings' => '管理App设置', 'role_export_content' => '导出内容', 'role_asset' => '资源许可', - 'roles_system_warning' => '请注意,具有上述三个权限中的任何一个都可以允许用户更改自己的特权或系统中其他人的特权。 只将具有这些权限的角色分配给受信任的用户。', + 'roles_system_warning' => '请注意,拥有上述三个权限中的任何一个都可以允许用户更改自己的权限或系统中其他人的权限。 请只将拥有这些权限的角色分配给你信任的用户。', 'role_asset_desc' => '对系统内资源的默认访问许可将由这些权限控制。单独设置在书籍,章节和页面上的权限将覆盖这里的权限设定。', 'role_asset_admins' => '管理员可自动获得对所有内容的访问权限,但这些选项可能会显示或隐藏UI选项。', 'role_all' => '全部的', From 9490457d044b51fbe330998ce37dcfe255038f55 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 30 Nov 2021 14:25:09 +0000 Subject: [PATCH 7/7] Applied StyleCI changes --- app/Auth/Permissions/PermissionService.php | 8 ++++---- app/Http/Middleware/ApiAuthenticate.php | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/Auth/Permissions/PermissionService.php b/app/Auth/Permissions/PermissionService.php index 4214861c2..59ff37dc9 100644 --- a/app/Auth/Permissions/PermissionService.php +++ b/app/Auth/Permissions/PermissionService.php @@ -625,7 +625,7 @@ class PermissionService })->where(function ($query) use ($tableDetails, $pageMorphClass) { /** @var Builder $query */ $query->where($tableDetails['entityTypeColumn'], '!=', $pageMorphClass) - ->orWhereExists(function(QueryBuilder $query) use ($tableDetails, $pageMorphClass) { + ->orWhereExists(function (QueryBuilder $query) use ($tableDetails, $pageMorphClass) { $query->select('id')->from('pages') ->whereColumn('pages.id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) ->where($tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'], '=', $pageMorphClass) @@ -645,10 +645,10 @@ class PermissionService public function filterRelatedEntity(string $entityClass, Builder $query, string $tableName, string $entityIdColumn): Builder { $fullEntityIdColumn = $tableName . '.' . $entityIdColumn; - $instance = new $entityClass; + $instance = new $entityClass(); $morphClass = $instance->getMorphClass(); - $existsQuery = function($permissionQuery) use ($fullEntityIdColumn, $morphClass) { + $existsQuery = function ($permissionQuery) use ($fullEntityIdColumn, $morphClass) { /** @var Builder $permissionQuery */ $permissionQuery->select('joint_permissions.role_id')->from('joint_permissions') ->whereColumn('joint_permissions.entity_id', '=', $fullEntityIdColumn) @@ -667,7 +667,7 @@ class PermissionService if ($instance instanceof Page) { // Prevent visibility of non-owned draft pages - $q->whereExists(function(QueryBuilder $query) use ($fullEntityIdColumn) { + $q->whereExists(function (QueryBuilder $query) use ($fullEntityIdColumn) { $query->select('id')->from('pages') ->whereColumn('pages.id', '=', $fullEntityIdColumn) ->where(function (QueryBuilder $query) { diff --git a/app/Http/Middleware/ApiAuthenticate.php b/app/Http/Middleware/ApiAuthenticate.php index 508efa028..5d621ac11 100644 --- a/app/Http/Middleware/ApiAuthenticate.php +++ b/app/Http/Middleware/ApiAuthenticate.php @@ -50,11 +50,12 @@ class ApiAuthenticate } /** - * Check if the active session user has API access + * Check if the active session user has API access. */ protected function sessionUserHasApiAccess(): bool { $hasApiPermission = user()->can('access-api'); + return $hasApiPermission && hasAppAccess(); }