From 6a1ed11ac6d65c6aa1c09633ac046d26ecb63ed4 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Wed, 14 Feb 2024 16:07:20 +0100 Subject: [PATCH 1/3] enh: Add details to CodeIntegrity setup check Signed-off-by: Ferdinand Thiessen --- apps/settings/css/settings.scss | 4 +- .../lib/Controller/CheckSetupController.php | 32 +-------------- .../lib/SetupChecks/CodeIntegrity.php | 41 ++++++++++++++++--- .../SettingsSetupChecksListItem.vue | 1 + 4 files changed, 40 insertions(+), 38 deletions(-) diff --git a/apps/settings/css/settings.scss b/apps/settings/css/settings.scss index e3d03150fbef9..19a47ac489bb5 100644 --- a/apps/settings/css/settings.scss +++ b/apps/settings/css/settings.scss @@ -581,9 +581,9 @@ doesnotexist:-o-prefocus, .strengthify-wrapper { } .trusted-domain-warning { - color: #fff; + color: var(--color-main-text); padding: 5px; - background: #ce3702; + background: var(--color-error); border-radius: 5px; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; } diff --git a/apps/settings/lib/Controller/CheckSetupController.php b/apps/settings/lib/Controller/CheckSetupController.php index f6c2a642e23c1..53abbbb401b26 100644 --- a/apps/settings/lib/Controller/CheckSetupController.php +++ b/apps/settings/lib/Controller/CheckSetupController.php @@ -70,37 +70,7 @@ public function getFailedIntegrityCheckFiles(): DataDisplayResponse { } if (!empty($completeResults)) { - $formattedTextResponse = 'Technical information -===================== -The following list covers which files have failed the integrity check. Please read -the previous linked documentation to learn more about the errors and how to fix -them. - -Results -======= -'; - foreach ($completeResults as $context => $contextResult) { - $formattedTextResponse .= "- $context\n"; - - foreach ($contextResult as $category => $result) { - $formattedTextResponse .= "\t- $category\n"; - if ($category !== 'EXCEPTION') { - foreach ($result as $key => $results) { - $formattedTextResponse .= "\t\t- $key\n"; - } - } else { - foreach ($result as $key => $results) { - $formattedTextResponse .= "\t\t- $results\n"; - } - } - } - } - - $formattedTextResponse .= ' -Raw output -========== -'; - $formattedTextResponse .= print_r($completeResults, true); + $formattedTextResponse = json_encode($completeResults, JSON_PRETTY_PRINT); } else { $formattedTextResponse = 'No errors have been found.'; } diff --git a/apps/settings/lib/SetupChecks/CodeIntegrity.php b/apps/settings/lib/SetupChecks/CodeIntegrity.php index ae99d70d8b496..c8a5ea2abceea 100644 --- a/apps/settings/lib/SetupChecks/CodeIntegrity.php +++ b/apps/settings/lib/SetupChecks/CodeIntegrity.php @@ -47,20 +47,51 @@ public function run(): SetupResult { if ($this->checker->hasPassedCheck()) { return SetupResult::success($this->l10n->t('No altered files')); } else { + $completeResults = $this->checker->getResults(); + $formattedTextResponse = ''; + if (!empty($completeResults)) { + $formattedTextResponse = '#### ' . $this->l10n->t('Technical information'); + $formattedTextResponse .= "\n" . $this->l10n->t('The following list covers which files have failed the integrity check.'); + $formattedTextResponse .= "\n"; + foreach ($completeResults as $context => $contextResult) { + $formattedTextResponse .= "- $context\n"; + + foreach ($contextResult as $category => $result) { + $categoryName = match($category) { + 'EXCEPTION' => $this->l10n->t('Exception'), + 'EXTRA_FILE' => $this->l10n->t('Unexpected file'), + 'FILE_MISSING' => $this->l10n->t('Missing file'), + 'INVALID_HASH' => $this->l10n->t('Invalid file (hash mismatch)'), + default => $category, + }; + $formattedTextResponse .= "\t- $categoryName\n"; + if ($category !== 'EXCEPTION') { + foreach ($result as $key => $results) { + $formattedTextResponse .= "\t\t- $key\n"; + } + } else { + foreach ($result as $key => $results) { + $formattedTextResponse .= "\t\t- $results\n"; + } + } + } + } + } + return SetupResult::error( - $this->l10n->t('Some files have not passed the integrity check. {link1} {link2}'), + $this->l10n->t('Some files have not passed the integrity check. {rawOutput} {rescan}') . "\n\n" . $formattedTextResponse, $this->urlGenerator->linkToDocs('admin-code-integrity'), [ - 'link1' => [ + 'rawOutput' => [ 'type' => 'highlight', 'id' => 'getFailedIntegrityCheckFiles', - 'name' => $this->l10n->t('List of invalid files…'), + 'name' => $this->l10n->t('Raw output …'), 'link' => $this->urlGenerator->linkToRoute('settings.CheckSetup.getFailedIntegrityCheckFiles'), ], - 'link2' => [ + 'rescan' => [ 'type' => 'highlight', 'id' => 'rescanFailedIntegrityCheck', - 'name' => $this->l10n->t('Rescan…'), + 'name' => $this->l10n->t('Rescan …'), 'link' => $this->urlGenerator->linkToRoute('settings.CheckSetup.rescanFailedIntegrityCheck'), ], ], diff --git a/apps/settings/src/components/SettingsSetupChecks/SettingsSetupChecksListItem.vue b/apps/settings/src/components/SettingsSetupChecks/SettingsSetupChecksListItem.vue index fa7a8ca01d909..3202b0f3a1a80 100644 --- a/apps/settings/src/components/SettingsSetupChecks/SettingsSetupChecksListItem.vue +++ b/apps/settings/src/components/SettingsSetupChecks/SettingsSetupChecksListItem.vue @@ -82,6 +82,7 @@ function parseRichObject(message: string, parameters?: IRichObjectParameters): s display: flex; align-items: start; flex-direction: row; + white-space: pre-wrap; &:hover { background-color: var(--color-background-hover); From 45edaa2ca6912cf2b99078f3814693f743a32219 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Sat, 27 Jun 2026 22:47:30 +0200 Subject: [PATCH 2/3] fix(settings): escape markdown special characters Signed-off-by: Ferdinand Thiessen --- .../settings/lib/SetupChecks/CodeIntegrity.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/settings/lib/SetupChecks/CodeIntegrity.php b/apps/settings/lib/SetupChecks/CodeIntegrity.php index c8a5ea2abceea..7c02f84f010e2 100644 --- a/apps/settings/lib/SetupChecks/CodeIntegrity.php +++ b/apps/settings/lib/SetupChecks/CodeIntegrity.php @@ -55,7 +55,7 @@ public function run(): SetupResult { $formattedTextResponse .= "\n"; foreach ($completeResults as $context => $contextResult) { $formattedTextResponse .= "- $context\n"; - + foreach ($contextResult as $category => $result) { $categoryName = match($category) { 'EXCEPTION' => $this->l10n->t('Exception'), @@ -67,11 +67,11 @@ public function run(): SetupResult { $formattedTextResponse .= "\t- $categoryName\n"; if ($category !== 'EXCEPTION') { foreach ($result as $key => $results) { - $formattedTextResponse .= "\t\t- $key\n"; + $formattedTextResponse .= "\t\t- '" . $this->escapeMarkdown($key) . "'\n"; } } else { foreach ($result as $key => $results) { - $formattedTextResponse .= "\t\t- $results\n"; + $formattedTextResponse .= "\t\t- " . $this->escapeMarkdown($results) . "\n"; } } } @@ -98,4 +98,16 @@ public function run(): SetupResult { ); } } + + /** + * Escape markdown text + * + * @param string $text The markdown text to escape + */ + private function escapeMarkdown(string $text): string { + $pattern = '/[-#*+`._[\]()!&<>_{}|]/'; + $replacement = fn ($matches): string => '\\' . $matches[0]; + + return preg_replace_callback($pattern, $replacement, $text) ?? $text; + } } From 9109f22f0b5a8776a2fca33fae5ddb88e57fd4dc Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Sat, 27 Jun 2026 22:48:16 +0200 Subject: [PATCH 3/3] fix(settings): properly show markdown setup check results Signed-off-by: Ferdinand Thiessen --- .../RichArguments/UnknownArgument.vue | 22 ++++++ .../SettingsSetupChecksListItem.vue | 76 +++++++++---------- .../src/composables/useRichArguments.ts | 76 +++++++++++++++++++ apps/settings/src/settings-types.ts | 17 ++++- 4 files changed, 151 insertions(+), 40 deletions(-) create mode 100644 apps/settings/src/components/RichArguments/UnknownArgument.vue create mode 100644 apps/settings/src/composables/useRichArguments.ts diff --git a/apps/settings/src/components/RichArguments/UnknownArgument.vue b/apps/settings/src/components/RichArguments/UnknownArgument.vue new file mode 100644 index 0000000000000..9fc914c0372d2 --- /dev/null +++ b/apps/settings/src/components/RichArguments/UnknownArgument.vue @@ -0,0 +1,22 @@ + + + + + + + diff --git a/apps/settings/src/components/SettingsSetupChecks/SettingsSetupChecksListItem.vue b/apps/settings/src/components/SettingsSetupChecks/SettingsSetupChecksListItem.vue index 3202b0f3a1a80..a370a797cc4f0 100644 --- a/apps/settings/src/components/SettingsSetupChecks/SettingsSetupChecksListItem.vue +++ b/apps/settings/src/components/SettingsSetupChecks/SettingsSetupChecksListItem.vue @@ -4,17 +4,23 @@ --> @@ -82,7 +73,6 @@ function parseRichObject(message: string, parameters?: IRichObjectParameters): s display: flex; align-items: start; flex-direction: row; - white-space: pre-wrap; &:hover { background-color: var(--color-background-hover); @@ -93,6 +83,14 @@ function parseRichObject(message: string, parameters?: IRichObjectParameters): s flex-direction: column; // align with icon padding-top: calc((var(--default-clickable-area) - 1lh) / 2); + width: 100%; + } + + &__header { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: var(--default-grid-baseline); } &__description { diff --git a/apps/settings/src/composables/useRichArguments.ts b/apps/settings/src/composables/useRichArguments.ts new file mode 100644 index 0000000000000..95468d6419d7d --- /dev/null +++ b/apps/settings/src/composables/useRichArguments.ts @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { MaybeRefOrGetter } from '@vueuse/core' +import type { Component } from 'vue' +import type { IRichObjectParameter, IRichObjectParameters } from '../settings-types.ts' + +import { toValue } from '@vueuse/core' +import { computed } from 'vue' +import NcUserBubble from '@nextcloud/vue/components/NcUserBubble' +import UnknownArgument from '../components/RichArguments/UnknownArgument.vue' + +export interface IRichArgument { + component: Component + props: Record +} + +/** + * Map an collection of rich text objects to rich arguments for the RichText component + * + * @param richObjects The rich text object + */ +export function mapRichObjectsToRichArguments(richObjects: IRichObjectParameters) { + const args: Record = {} + + for (const richObjectName in richObjects) { + args[richObjectName] = mapRichObjectToRichArgument(richObjects[richObjectName]) + } + + return args +} + +/** + * Map rich text object to rich argument for the RichText component + * + * @param richObject - The rich text object + */ +export function mapRichObjectToRichArgument(richObject: IRichObjectParameter): IRichArgument | string { + switch (richObject.type) { + case 'user': + return { + component: NcUserBubble as Component, + props: { + displayName: richObject.name, + user: richObject.id, + url: richObject.link, + }, + } + case 'group': + return { + component: NcUserBubble as Component, + props: { + avatarImage: 'icon-group', + displayName: richObject.name, + url: richObject.link, + primary: true, + }, + } + default: + return { + component: UnknownArgument, + props: richObject, + } + } +} + +/** + * Reactively map rich objects to rich arguments for use with NcRichText + * + * @param objects Map of RichObjects + */ +export function useRichArguments(objects: MaybeRefOrGetter) { + return computed(() => mapRichObjectsToRichArguments(toValue(objects))) +} diff --git a/apps/settings/src/settings-types.ts b/apps/settings/src/settings-types.ts index 53cb321929aa5..752a9391cf65f 100644 --- a/apps/settings/src/settings-types.ts +++ b/apps/settings/src/settings-types.ts @@ -4,8 +4,23 @@ */ export interface IRichObjectParameter { - [index: string]: string + /** + * ID of the rich object + */ + id: string | number + /** + * Name of the rich object + */ + name: string + /** + * Type of the rich object + */ type: string + + /** + * Additional rich object properties + */ + [key: string]: unknown } export type IRichObjectParameters = Record