From fd27ad11050f91c493a16de25f08672d7000cf44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20H=C3=A4rter?= Date: Wed, 6 Nov 2024 14:17:26 +0100 Subject: [PATCH 01/21] Issue #3915: Improved ValueSet error message for Date dynamic field driver. --- Kernel/System/DynamicField/Driver/Date.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Kernel/System/DynamicField/Driver/Date.pm b/Kernel/System/DynamicField/Driver/Date.pm index 0d04946d70..6e762591bb 100644 --- a/Kernel/System/DynamicField/Driver/Date.pm +++ b/Kernel/System/DynamicField/Driver/Date.pm @@ -122,8 +122,8 @@ sub ValueSet { if ( $Param{Value} && $Param{Value} !~ m{\A \d{4}-\d{2}-\d{2}\s00:00:00 \z}xms ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', - Message => "The value for the field Date is invalid!\n" - . "The date must be valid and the time must be 00:00:00", + Message => "The value $Param{Value} for the field Date is invalid!\n" + . "The date must be in format 0000-00-00 and the time must be 00:00:00", ); return; } From 07b14f050849c67d82c86f3457ca5e3fa48da2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20H=C3=A4rter?= Date: Thu, 7 Nov 2024 09:27:24 +0100 Subject: [PATCH 02/21] Issue #3918: Fix rendering of ArticleDynamicFieldUpdate history entries. --- Kernel/Modules/AgentTicketHistory.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Kernel/Modules/AgentTicketHistory.pm b/Kernel/Modules/AgentTicketHistory.pm index 1616b0619b..866876f751 100644 --- a/Kernel/Modules/AgentTicketHistory.pm +++ b/Kernel/Modules/AgentTicketHistory.pm @@ -154,10 +154,10 @@ sub Run { # values stored previously in older format might not be compatible with new human readable form. # Please see bug#11520 for more information. # - # HistoryType: TicketDynamicFieldUpdate + # HistoryType: ArticleDynamicFieldUpdate & TicketDynamicFieldUpdate # - Old: %%FieldName%%$FieldName%%Value%%$HistoryValue%%OldValue%%$HistoryOldValue # - New: %%$FieldName%%$HistoryOldValue%%$HistoryValue - if ( $Data->{HistoryType} eq 'TicketDynamicFieldUpdate' ) { + if ( $Data->{HistoryType} eq 'ArticleDynamicFieldUpdate' || $Data->{HistoryType} eq 'TicketDynamicFieldUpdate' ) { @Values = ( $Values[1], $Values[5] // '', $Values[3] // '' ); } From ac3fa4cc476f01c2b7abd3a978412cc08f2647d3 Mon Sep 17 00:00:00 2001 From: StefanRother-OTOBO Date: Thu, 7 Nov 2024 11:01:59 +0100 Subject: [PATCH 03/21] Issue #3673: Use different dockerfiles for nginx-kerberos 10.0 and 10.1 --- .../docker_image_update_autobuild.yml | 18 +++++++++++-- .../workflows/docker_image_update_checker.yml | 25 +++++++++---------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/.github/workflows/docker_image_update_autobuild.yml b/.github/workflows/docker_image_update_autobuild.yml index 731499bed6..f4fd282282 100644 --- a/.github/workflows/docker_image_update_autobuild.yml +++ b/.github/workflows/docker_image_update_autobuild.yml @@ -42,7 +42,22 @@ jobs: target: 'otobo-web' docker_tag: 'latest-11_0-autobuild' base_image: 'perl:5.38-bookworm' - # common info for every target; for nginx this includes the base image + + # otobo-nginx-kerberos-webproxy uses different dockerfiles in 10.0 and 10.1 + - + target: 'otobo-nginx-kerberos-webproxy' + docker_tag: 'latest-10_0-autobuild' + dockerfile: 'otobo.nginx-kerberos.dockerfile' + - + target: 'otobo-nginx-kerberos-webproxy' + docker_tag: 'latest-10_1-autobuild' + dockerfile: 'otobo.nginx-kerberos.dockerfile' + - + target: 'otobo-nginx-kerberos-webproxy' + docker_tag: 'latest-11_0-autobuild' + dockerfile: 'otobo.nginx.dockerfile' + + # common info for every target; for nginx this includes the base imagie - target: 'otobo-web' dockerfile: 'otobo.web.dockerfile' @@ -56,7 +71,6 @@ jobs: base_image: 'nginx:mainline' - target: 'otobo-nginx-kerberos-webproxy' - dockerfile: 'otobo.nginx.dockerfile' context: 'scripts/nginx' repository: 'rotheross/otobo-nginx-kerberos-webproxy' base_image: 'nginx:mainline' diff --git a/.github/workflows/docker_image_update_checker.yml b/.github/workflows/docker_image_update_checker.yml index 75e5a20ec3..4aa0c0b795 100644 --- a/.github/workflows/docker_image_update_checker.yml +++ b/.github/workflows/docker_image_update_checker.yml @@ -19,7 +19,6 @@ on: jobs: CheckDockerImageUpdate: - runs-on: 'ubuntu-latest' strategy: # create different images @@ -39,7 +38,7 @@ jobs: dockerfile: 'otobo.elasticsearch.dockerfile' context: 'scripts/elasticsearch' repository: 'rotheross/otobo-elasticsearch' - base_image: 'docker.elastic.co/elasticsearch/elasticsearch:7.17.25' + base_image: 'elasticsearch:7.17.3' - target: 'otobo-nginx-webproxy' dockerfile: 'otobo.nginx.dockerfile' @@ -59,13 +58,13 @@ jobs: repository: 'rotheross/otobo-selenium-chrome' base_image: 'selenium/standalone-chrome-debug:3.141.59-20210422' - + runs-on: ${{ matrix.target }} steps: - name: Setting up the environment file run: | - patch="${{ matrix.patch }}" - docker_tag="rel-${patch}_test" + patch=${{ matrix.patch }} + docker_tag="rel-${patch}" mixed_case_repository="${{ github.repository }}" lowercased_repository="${mixed_case_repository,,}" build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ') @@ -120,8 +119,8 @@ jobs: uses: docker/build-push-action@v6 with: load: true - context: . - file: otobo.web.dockerfile + context: ${{ matrix.context }} + file: ${{ matrix.dockerfile }} pull: true build-args: | BUILD_DATE=${{ env.otobo_build_date }} @@ -129,7 +128,7 @@ jobs: GIT_REPO=${{ github.repositoryUrl }} GIT_BRANCH=${{ env.otobo_branch }} GIT_COMMIT=${{ env.otobo_commit }} - target: otobo-web + target: ${{ matrix.target }} tags: ${{ env.otobo_image }} cache-from: type=gha cache-to: type=gha,mode=max` @@ -137,7 +136,7 @@ jobs: - # otobo_first_time hasn't run yet, so /opt/otobo is still empty name: Info - if: steps.check.outputs.needs-updating == 'true' + if: ${{ steps.check.outputs.needs-updating == 'true' && matrix.target == 'otobo-web' }} run: | docker run --rm -w /opt/otobo_install/otobo_next --entrypoint /bin/bash $otobo_image -c "more git-repo.txt git-branch.txt git-commit.txt RELEASE | cat" @@ -158,8 +157,8 @@ jobs: uses: docker/build-push-action@v6 with: push: true - context: . - file: otobo.web.dockerfile + context: ${{ matrix.context }} + file: ${{ matrix.dockerfile }} pull: true build-args: | BUILD_DATE=${{ env.otobo_build_date }} @@ -167,7 +166,7 @@ jobs: GIT_REPO=${{ github.repositoryUrl }} GIT_BRANCH=${{ env.otobo_branch }} GIT_COMMIT=${{ env.otobo_commit }} - target: otobo-web + target: ${{ matrix.target }} tags: ${{ env.otobo_image }} cache-from: type=gha - cache-to: type=gha,mode=max` + cache-to: type=gha,mode=max From 1a6945ef735b37098788125264268a1bdefe570c Mon Sep 17 00:00:00 2001 From: Charly Zauleck Date: Thu, 24 Oct 2024 09:06:16 +0100 Subject: [PATCH 04/21] Issue #3482: disable datepickers in disabled dynamic fields --- var/httpd/htdocs/js/Core.UI.Datepicker.js | 21 ++++++++++++------- .../skins/Agent/default/css/Core.Default.css | 9 ++++++++ .../Customer/default/css/Core.Default.css | 4 ++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/var/httpd/htdocs/js/Core.UI.Datepicker.js b/var/httpd/htdocs/js/Core.UI.Datepicker.js index edf8a504d4..eefb8d273c 100644 --- a/var/httpd/htdocs/js/Core.UI.Datepicker.js +++ b/var/httpd/htdocs/js/Core.UI.Datepicker.js @@ -121,7 +121,8 @@ Core.UI.Datepicker = (function (TargetNS) { var $DatepickerElement, HasDateSelectBoxes = false, Options, - ErrorMessage; + ErrorMessage, + Disabled = false; if (typeof Element.VacationDays === 'object') { Core.Config.Set('Datepicker.VacationDays', Element.VacationDays); @@ -159,6 +160,7 @@ Core.UI.Datepicker = (function (TargetNS) { // Ignore in this case. Element.Day.length ) { + Disabled = Element.Day.get(0).disabled && Element.Month.get(0).disabled && Element.Year.get(0).disabled; $DatepickerElement = $('').attr('type', 'hidden').attr('id', 'Datepicker' + DatepickerCount); // insert DatepickerElement @@ -288,9 +290,11 @@ Core.UI.Datepicker = (function (TargetNS) { // Check if one additional DOM node is already present. if (!$('#' + Core.App.EscapeSelector(Element.Day.attr('id')) + 'DatepickerIcon').length) { + let disableDatepickerHTML = Disabled ? ' DisabledLink' : ''; + // add datepicker icon and click event if ( Core.Config.Get('SessionName') === Core.Config.Get('CustomerPanelSessionName') ) { - var Icon = $(''); + var Icon = $(''); // auto activate dynamic field on click on Datepicker var DateContainer = $DatepickerElement.parent(); @@ -304,7 +308,7 @@ Core.UI.Datepicker = (function (TargetNS) { $DatepickerElement.after(Icon); } else { - $DatepickerElement.after(''); + $DatepickerElement.after(''); } if (Element.DateInFuture) { @@ -331,10 +335,13 @@ Core.UI.Datepicker = (function (TargetNS) { } } - $('#' + Core.App.EscapeSelector(Element.Day.attr('id')) + 'DatepickerIcon').off('click.Datepicker').on('click.Datepicker', function () { - $DatepickerElement.datepicker('show'); - return false; - }); + + if (!Disabled) { + $('#' + Core.App.EscapeSelector(Element.Day.attr('id')) + 'DatepickerIcon').off('click.Datepicker').on('click.Datepicker', function () { + $DatepickerElement.datepicker('show'); + return false; + }); + }; //adjust z-index of date picker to prevent overlapping with richtexteditors $DatepickerElement.css('position', 'relative'); diff --git a/var/httpd/htdocs/skins/Agent/default/css/Core.Default.css b/var/httpd/htdocs/skins/Agent/default/css/Core.Default.css index 2d7648cbfa..8d0c651524 100644 --- a/var/httpd/htdocs/skins/Agent/default/css/Core.Default.css +++ b/var/httpd/htdocs/skins/Agent/default/css/Core.Default.css @@ -557,6 +557,11 @@ table.AttachmentList.DataTable thead th { position: absolute !important; } +a.DisabledLink { + pointer-events: none; + cursor: default; +} + /** * @subsection Spacings */ @@ -1147,6 +1152,10 @@ span.ImportantArticles i { color: var(--colTextDark); } +.DatepickerIcon.DisabledLink { + color: var(--colTextLight); +} + .RTL .DatepickerIcon { margin: 0 4px 0 0; } diff --git a/var/httpd/htdocs/skins/Customer/default/css/Core.Default.css b/var/httpd/htdocs/skins/Customer/default/css/Core.Default.css index 65f956f475..c56069ad5e 100644 --- a/var/httpd/htdocs/skins/Customer/default/css/Core.Default.css +++ b/var/httpd/htdocs/skins/Customer/default/css/Core.Default.css @@ -85,6 +85,10 @@ h3 { color: inherit; } +a.DisabledLink { + pointer-events: none; +} + @media only screen and (max-width: 767px) { h1 { From 2660b7127dfc464c69c723dca453bf641a7560d8 Mon Sep 17 00:00:00 2001 From: Charly Zauleck Date: Mon, 28 Oct 2024 09:23:56 +0000 Subject: [PATCH 05/21] Issue #3482: cleaned up disabling of datepickers --- Kernel/Output/HTML/Layout.pm | 10 ++++++---- Kernel/System/DynamicField/Driver/BaseDateTime.pm | 5 +++++ Kernel/System/DynamicField/Driver/Checkbox.pm | 5 +++++ Kernel/System/DynamicField/Driver/Date.pm | 5 +++++ var/httpd/htdocs/js/Core.UI.Datepicker.js | 9 ++++----- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Kernel/Output/HTML/Layout.pm b/Kernel/Output/HTML/Layout.pm index 86b1638553..dac877d1bb 100644 --- a/Kernel/Output/HTML/Layout.pm +++ b/Kernel/Output/HTML/Layout.pm @@ -3974,7 +3974,7 @@ sub BuildDateSelection { if ( $Prefix !~ /^DynamicField_/ || $Suffix ne '_Template' ) { my $DatepickerJS = ' Core.UI.Datepicker.Init({ - Day: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Day"' . ( $Suffix ? ' + Core.App.EscapeSelector("' . $Suffix . '")' : '' ) . '), + Day: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Day"' . ( $Suffix ? ' + Core.App.EscapeSelector("' . $Suffix . '")' : '' ) . '), Month: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Month"' . ( $Suffix ? ' + Core.App.EscapeSelector("' . $Suffix . '")' : '' ) . '), Year: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Year"' . ( $Suffix ? ' + Core.App.EscapeSelector("' . $Suffix . '")' : '' ) . '), Hour: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Hour"' . ( $Suffix ? ' + Core.App.EscapeSelector("' . $Suffix . '")' : '' ) . '), @@ -3983,6 +3983,8 @@ sub BuildDateSelection { DateInFuture: ' . ( $ValidateDateInFuture ? 'true' : 'false' ) . ', DateNotInFuture: ' . ( $ValidateDateNotInFuture ? 'true' : 'false' ) . ', WeekDayStart: ' . $WeekDayStart . ' + }, { + Disabled: ' . ( $Param{Disabled} ? 'true' : 'false' ) . ' });'; $Self->AddJSOnDocumentComplete( Code => $DatepickerJS ); @@ -5303,9 +5305,9 @@ sub RichTextDocumentComplete { ); # verify HTML document - my $CustomerInterface = ($Self->{SessionSource} && ($Self->{SessionSource} eq 'CustomerInterface')) ? 1 : 0; - my $HTMLString = $Kernel::OM->Get('Kernel::System::HTMLUtils')->DocumentComplete( - String => $StringRef->$*, + my $CustomerInterface = ( $Self->{SessionSource} && ( $Self->{SessionSource} eq 'CustomerInterface' ) ) ? 1 : 0; + my $HTMLString = $Kernel::OM->Get('Kernel::System::HTMLUtils')->DocumentComplete( + String => $StringRef->$*, CustomerInterface => $CustomerInterface ); diff --git a/Kernel/System/DynamicField/Driver/BaseDateTime.pm b/Kernel/System/DynamicField/Driver/BaseDateTime.pm index 7869b6147a..5fd2155ab1 100644 --- a/Kernel/System/DynamicField/Driver/BaseDateTime.pm +++ b/Kernel/System/DynamicField/Driver/BaseDateTime.pm @@ -264,6 +264,11 @@ sub EditFieldRender { $FieldClass .= ' Validate_Required'; } + # set readonly css class + if ( $Param{Readonly} ) { + $FieldClass .= ' ReadOnly'; + } + # set error css class if ( $Param{ServerError} ) { $FieldClass .= ' ServerError'; diff --git a/Kernel/System/DynamicField/Driver/Checkbox.pm b/Kernel/System/DynamicField/Driver/Checkbox.pm index 5f9b663dd3..9f45cc2379 100644 --- a/Kernel/System/DynamicField/Driver/Checkbox.pm +++ b/Kernel/System/DynamicField/Driver/Checkbox.pm @@ -373,6 +373,11 @@ sub EditFieldRender { $FieldClass .= ' Validate_Required'; } + # set readonly css class + if ( $Param{Readonly} ) { + $FieldClass .= ' ReadOnly'; + } + # set error css class if ( $Param{ServerError} ) { $FieldClass .= ' ServerError'; diff --git a/Kernel/System/DynamicField/Driver/Date.pm b/Kernel/System/DynamicField/Driver/Date.pm index 4269c0890c..4d0e4c4c24 100644 --- a/Kernel/System/DynamicField/Driver/Date.pm +++ b/Kernel/System/DynamicField/Driver/Date.pm @@ -370,6 +370,11 @@ sub EditFieldRender { $FieldClass .= ' Validate_Required'; } + # set readonly css class + if ( $Param{Readonly} ) { + $FieldClass .= ' ReadOnly'; + } + # set error css class if ( $Param{ServerError} ) { $FieldClass .= ' ServerError'; diff --git a/var/httpd/htdocs/js/Core.UI.Datepicker.js b/var/httpd/htdocs/js/Core.UI.Datepicker.js index eefb8d273c..2bd0e8da87 100644 --- a/var/httpd/htdocs/js/Core.UI.Datepicker.js +++ b/var/httpd/htdocs/js/Core.UI.Datepicker.js @@ -116,13 +116,12 @@ Core.UI.Datepicker = (function (TargetNS) { * @description * This function initializes the datepicker on the defined elements. */ - TargetNS.Init = function (Element) { + TargetNS.Init = function (Element, Attributes={}) { var $DatepickerElement, HasDateSelectBoxes = false, Options, - ErrorMessage, - Disabled = false; + ErrorMessage; if (typeof Element.VacationDays === 'object') { Core.Config.Set('Datepicker.VacationDays', Element.VacationDays); @@ -150,6 +149,8 @@ Core.UI.Datepicker = (function (TargetNS) { // Increment number of initialized datepickers on this site DatepickerCount++; + let Disabled = Attributes.Disabled || false; + // Check, if datepicker is used with three input element or with three select boxes if (typeof Element === 'object' && typeof Element.Day !== 'undefined' && @@ -160,8 +161,6 @@ Core.UI.Datepicker = (function (TargetNS) { // Ignore in this case. Element.Day.length ) { - Disabled = Element.Day.get(0).disabled && Element.Month.get(0).disabled && Element.Year.get(0).disabled; - $DatepickerElement = $('').attr('type', 'hidden').attr('id', 'Datepicker' + DatepickerCount); // insert DatepickerElement if ( Core.Config.Get('SessionName') === Core.Config.Get('CustomerPanelSessionName') ) { From 3087246fdbd0283cae5953f46bf3027e231e4626 Mon Sep 17 00:00:00 2001 From: Charly Zauleck Date: Mon, 28 Oct 2024 09:28:41 +0000 Subject: [PATCH 06/21] Issue #3824: disable editing for readonly richtext dynamic fields --- Kernel/System/DynamicField/Driver/RichText.pm | 5 +++++ var/httpd/htdocs/js/Core.UI.RichTextEditor.js | 5 +++++ .../skins/Customer/default/css/CKEditorCustomStyles.css | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/Kernel/System/DynamicField/Driver/RichText.pm b/Kernel/System/DynamicField/Driver/RichText.pm index d675c2b024..a76a876bae 100644 --- a/Kernel/System/DynamicField/Driver/RichText.pm +++ b/Kernel/System/DynamicField/Driver/RichText.pm @@ -129,6 +129,11 @@ sub EditFieldRender { $FieldClass .= ' Validate_Required'; } + # set readonly css class + if ( $Param{Readonly} ) { + $FieldClass .= ' ReadOnly'; + } + # set error css class if ( $Param{ServerError} ) { $FieldClass .= ' ServerError'; diff --git a/var/httpd/htdocs/js/Core.UI.RichTextEditor.js b/var/httpd/htdocs/js/Core.UI.RichTextEditor.js index 66d24fbd6f..0e5b63f4f0 100644 --- a/var/httpd/htdocs/js/Core.UI.RichTextEditor.js +++ b/var/httpd/htdocs/js/Core.UI.RichTextEditor.js @@ -272,6 +272,11 @@ Core.UI.RichTextEditor = (function (TargetNS) { $domEditableElement = $($EditorArea).closest(".RichTextHolder"); } + //Set to Readonly mode if required + if ($EditorArea.hasClass('ReadOnly')) { + editor.enableReadOnlyMode('DF_Readonly'); + } + var sourceEditingActive = false; $domEditableElement.resizable(); diff --git a/var/httpd/htdocs/skins/Customer/default/css/CKEditorCustomStyles.css b/var/httpd/htdocs/skins/Customer/default/css/CKEditorCustomStyles.css index 51b01beb88..d92a851870 100644 --- a/var/httpd/htdocs/skins/Customer/default/css/CKEditorCustomStyles.css +++ b/var/httpd/htdocs/skins/Customer/default/css/CKEditorCustomStyles.css @@ -82,3 +82,8 @@ along with this program. If not, see . font-size: 16px; color: #ea2400; } + +textarea.ReadOnly ~ .ck .ck-editor__editable { + box-shadow: inset 0px 0px 8px 2px #bfbfce; + box-shadow: inset 0px 0px 8px 2px var(--colBGDark); +} From 21ef91d104f2dd7244eb38981f46e5ff1033119b Mon Sep 17 00:00:00 2001 From: Charly Zauleck Date: Wed, 30 Oct 2024 09:19:23 +0000 Subject: [PATCH 07/21] changed css class ReadOnly to Readonly --- Kernel/System/DynamicField/Driver/BaseDateTime.pm | 2 +- Kernel/System/DynamicField/Driver/Checkbox.pm | 2 +- Kernel/System/DynamicField/Driver/Date.pm | 2 +- Kernel/System/DynamicField/Driver/RichText.pm | 2 +- var/httpd/htdocs/js/Core.UI.RichTextEditor.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Kernel/System/DynamicField/Driver/BaseDateTime.pm b/Kernel/System/DynamicField/Driver/BaseDateTime.pm index 5fd2155ab1..1f4e23317b 100644 --- a/Kernel/System/DynamicField/Driver/BaseDateTime.pm +++ b/Kernel/System/DynamicField/Driver/BaseDateTime.pm @@ -266,7 +266,7 @@ sub EditFieldRender { # set readonly css class if ( $Param{Readonly} ) { - $FieldClass .= ' ReadOnly'; + $FieldClass .= ' Readonly'; } # set error css class diff --git a/Kernel/System/DynamicField/Driver/Checkbox.pm b/Kernel/System/DynamicField/Driver/Checkbox.pm index 9f45cc2379..2660260216 100644 --- a/Kernel/System/DynamicField/Driver/Checkbox.pm +++ b/Kernel/System/DynamicField/Driver/Checkbox.pm @@ -375,7 +375,7 @@ sub EditFieldRender { # set readonly css class if ( $Param{Readonly} ) { - $FieldClass .= ' ReadOnly'; + $FieldClass .= ' Readonly'; } # set error css class diff --git a/Kernel/System/DynamicField/Driver/Date.pm b/Kernel/System/DynamicField/Driver/Date.pm index 4d0e4c4c24..ec296ed6f7 100644 --- a/Kernel/System/DynamicField/Driver/Date.pm +++ b/Kernel/System/DynamicField/Driver/Date.pm @@ -372,7 +372,7 @@ sub EditFieldRender { # set readonly css class if ( $Param{Readonly} ) { - $FieldClass .= ' ReadOnly'; + $FieldClass .= ' Readonly'; } # set error css class diff --git a/Kernel/System/DynamicField/Driver/RichText.pm b/Kernel/System/DynamicField/Driver/RichText.pm index a76a876bae..e3d0d7cb91 100644 --- a/Kernel/System/DynamicField/Driver/RichText.pm +++ b/Kernel/System/DynamicField/Driver/RichText.pm @@ -131,7 +131,7 @@ sub EditFieldRender { # set readonly css class if ( $Param{Readonly} ) { - $FieldClass .= ' ReadOnly'; + $FieldClass .= ' Readonly'; } # set error css class diff --git a/var/httpd/htdocs/js/Core.UI.RichTextEditor.js b/var/httpd/htdocs/js/Core.UI.RichTextEditor.js index 0e5b63f4f0..cdf147ba7d 100644 --- a/var/httpd/htdocs/js/Core.UI.RichTextEditor.js +++ b/var/httpd/htdocs/js/Core.UI.RichTextEditor.js @@ -273,7 +273,7 @@ Core.UI.RichTextEditor = (function (TargetNS) { } //Set to Readonly mode if required - if ($EditorArea.hasClass('ReadOnly')) { + if ($EditorArea.hasClass('Readonly')) { editor.enableReadOnlyMode('DF_Readonly'); } From 7128d19be70f4019572ee8038b23c214bc921269 Mon Sep 17 00:00:00 2001 From: Charly Zauleck Date: Wed, 6 Nov 2024 09:29:56 +0000 Subject: [PATCH 08/21] Issue #3482: cleanup and comments --- var/httpd/htdocs/js/Core.UI.Datepicker.js | 2 ++ .../htdocs/skins/Customer/default/css/CKEditorCustomStyles.css | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/var/httpd/htdocs/js/Core.UI.Datepicker.js b/var/httpd/htdocs/js/Core.UI.Datepicker.js index 2bd0e8da87..5e759b389f 100644 --- a/var/httpd/htdocs/js/Core.UI.Datepicker.js +++ b/var/httpd/htdocs/js/Core.UI.Datepicker.js @@ -113,6 +113,8 @@ Core.UI.Datepicker = (function (TargetNS) { * @returns {Boolean} false, if Parameter Element is not of the correct type. * @param {jQueryObject|Object} Element - The jQuery object of a text input field which should get a datepicker. * Or a hash with the Keys 'Year', 'Month' and 'Day' and as values the jQueryObjects of the select drop downs. + * @param {Object} [Attributes] - Optional Attributes to be passed to the datepicker. Possible Attributes: + * - Disabled: Set to true to disable the datepicker. * @description * This function initializes the datepicker on the defined elements. */ diff --git a/var/httpd/htdocs/skins/Customer/default/css/CKEditorCustomStyles.css b/var/httpd/htdocs/skins/Customer/default/css/CKEditorCustomStyles.css index d92a851870..cf72c32489 100644 --- a/var/httpd/htdocs/skins/Customer/default/css/CKEditorCustomStyles.css +++ b/var/httpd/htdocs/skins/Customer/default/css/CKEditorCustomStyles.css @@ -83,7 +83,7 @@ along with this program. If not, see . color: #ea2400; } -textarea.ReadOnly ~ .ck .ck-editor__editable { +textarea.Readonly ~ .ck .ck-editor__editable { box-shadow: inset 0px 0px 8px 2px #bfbfce; box-shadow: inset 0px 0px 8px 2px var(--colBGDark); } From bb4475f4570fd7e67f842020afac290979328ee5 Mon Sep 17 00:00:00 2001 From: Charly Zauleck Date: Thu, 7 Nov 2024 13:11:44 +0000 Subject: [PATCH 09/21] Issue #3921: fixed z-index of tooltips --- .../htdocs/skins/Customer/default/css/Core.InputFields.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/var/httpd/htdocs/skins/Customer/default/css/Core.InputFields.css b/var/httpd/htdocs/skins/Customer/default/css/Core.InputFields.css index 1f298899d5..4810bb0e6d 100644 --- a/var/httpd/htdocs/skins/Customer/default/css/Core.InputFields.css +++ b/var/httpd/htdocs/skins/Customer/default/css/Core.InputFields.css @@ -607,7 +607,6 @@ input[readonly] { top: 21px; left: -32px; margin-left: -22px; /* +4px for empty spaces */ - z-index: 1001; } .Tooltip > .Content { @@ -622,11 +621,14 @@ input[readonly] { } .oooTooltip i { + position: relative; + z-index: 100; font-size: 18px; } .oooTooltip > .Content { - z-index: 1; + z-index: 1100; + position: relative; display: none; color: #00023c; color: var(--colTextDark); @@ -638,6 +640,7 @@ input[readonly] { left: -260px; top: -8px; width: 280px; + z-index: 1100; } .TooltipContainer { From ea9fe4346521184bec9ebd0bdcf57252933fc381 Mon Sep 17 00:00:00 2001 From: Charly Zauleck Date: Mon, 11 Nov 2024 11:55:29 +0000 Subject: [PATCH 10/21] Issue #3923: fixed shadows in customer ticket zoom --- var/httpd/htdocs/skins/Customer/default/css/Core.TicketZoom.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/var/httpd/htdocs/skins/Customer/default/css/Core.TicketZoom.css b/var/httpd/htdocs/skins/Customer/default/css/Core.TicketZoom.css index 7bb8c0c5bb..713f78aea1 100644 --- a/var/httpd/htdocs/skins/Customer/default/css/Core.TicketZoom.css +++ b/var/httpd/htdocs/skins/Customer/default/css/Core.TicketZoom.css @@ -392,7 +392,7 @@ button.oooM.ActivitySubmitButton { height: auto; border-radius: 15px; box-shadow: 0 1px 4px 0 rgba(4, 0, 71, 0.16); - box-shadow: 0 12px 16px 0 var(--colShadowDark); + box-shadow: 0 1px 4px 0 var(--colShadowDark); background-color: #ffffff; } From 7044a27089793733931e3b9e616dc1db14c6d82b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20H=C3=A4rter?= Date: Wed, 6 Nov 2024 15:06:56 +0100 Subject: [PATCH 11/21] Issue #3914: Included webservice invoker trigger console command from ITSMConfigurationManagement package. --- .../Command/Maint/WebService/Trigger.pm | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 Kernel/System/Console/Command/Maint/WebService/Trigger.pm diff --git a/Kernel/System/Console/Command/Maint/WebService/Trigger.pm b/Kernel/System/Console/Command/Maint/WebService/Trigger.pm new file mode 100644 index 0000000000..f87b1cafbe --- /dev/null +++ b/Kernel/System/Console/Command/Maint/WebService/Trigger.pm @@ -0,0 +1,98 @@ +# -- +# OTOBO is a web-based ticketing system for service organisations. +# -- +# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ +# Copyright (C) 2019-2024 Rother OSS GmbH, https://otobo.io/ +# -- +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# -- + +package Kernel::System::Console::Command::Maint::WebService::Trigger; + +use strict; +use warnings; + +use parent qw(Kernel::System::Console::BaseCommand); + +use Kernel::System::VariableCheck qw(:all); + +our @ObjectDependencies = ( + 'Kernel::GenericInterface::Requester', + 'Kernel::System::Daemon::SchedulerDB', + 'Kernel::System::GenericInterface::Webservice', +); + +sub Configure { + my ( $Self, %Param ) = @_; + + $Self->Description('Triggers a given Invoker webservice.'); + $Self->AddArgument( + Name => 'WebServiceName', + Description => "Select name of web service to be triggered.", + Required => 1, + HasValue => 1, + ValueRegex => qr/./smx, + ); + $Self->AddArgument( + Name => 'Invoker', + Description => "Select Invoker to be triggered.", + Required => 1, + HasValue => 1, + ValueRegex => qr/./, + ); + + return; +} + +sub PreRun { + my ( $Self, %Param ) = @_; + + my $Invoker = $Self->GetArgument('Invoker'); + my $WebserviceName = $Self->GetArgument('WebServiceName'); + + # Check if all requirements are met (web service exists and has needed method). + my $Webservice + = $Kernel::OM->Get('Kernel::System::GenericInterface::Webservice')->WebserviceGet( Name => $WebserviceName ); + die "Required web service '$WebserviceName' does not exist!\n" if !IsHashRefWithData($Webservice); + die "Required web service '$WebserviceName' is invalid!\n" if $Webservice->{ValidID} ne 1; + + my $InvokerControllerMapping + = $Webservice->{Config}->{Requester}->{Transport}->{Config}->{InvokerControllerMapping}; + die "Web service '$WebserviceName' does not contain required REST controller mapping!\n" + if !IsHashRefWithData($InvokerControllerMapping); + die "Web service '$WebserviceName' does not contain the Invoker '$Invoker'!" + if !IsHashRefWithData( $InvokerControllerMapping->{$Invoker} ); + + # Remember data for task. + $Self->{InvokerTaskData} = { + WebserviceID => $Webservice->{ID}, + Invoker => $Invoker, + Data => { Dummy => 1 }, + }; + + return; +} + +sub Run { + my ( $Self, %Param ) = @_; + + my $Invoker = $Self->GetArgument('Invoker'); + + $Self->Print( + "Triggering $Invoker for immediate (asynchronous) execution.\n" + ); + + my $Result = $Kernel::OM->Get('Kernel::GenericInterface::Requester')->Run( $Self->{InvokerTaskData}->%* ); + + $Self->Print("Done.\n"); + return $Self->ExitCodeOk(); +} + +1; From 3fd222f96cb1e7653fb8d4c0ab5f11b660b346bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20H=C3=A4rter?= Date: Mon, 11 Nov 2024 13:45:52 +0100 Subject: [PATCH 12/21] Issue #3914: Moved console command to more logical location. --- .../Command/Maint/{WebService => GenericInterface}/Trigger.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Kernel/System/Console/Command/Maint/{WebService => GenericInterface}/Trigger.pm (97%) diff --git a/Kernel/System/Console/Command/Maint/WebService/Trigger.pm b/Kernel/System/Console/Command/Maint/GenericInterface/Trigger.pm similarity index 97% rename from Kernel/System/Console/Command/Maint/WebService/Trigger.pm rename to Kernel/System/Console/Command/Maint/GenericInterface/Trigger.pm index f87b1cafbe..dd24325d21 100644 --- a/Kernel/System/Console/Command/Maint/WebService/Trigger.pm +++ b/Kernel/System/Console/Command/Maint/GenericInterface/Trigger.pm @@ -14,7 +14,7 @@ # along with this program. If not, see . # -- -package Kernel::System::Console::Command::Maint::WebService::Trigger; +package Kernel::System::Console::Command::Maint::GenericInterface::Trigger; use strict; use warnings; From adf2d852eb0c142cace7891bfdedb6cf7f698a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20H=C3=A4rter?= Date: Mon, 11 Nov 2024 15:37:08 +0100 Subject: [PATCH 13/21] Issue #3914: Improved module structure. --- .../Command/Maint/GenericInterface/Trigger.pm | 52 +++++++++++++------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/Kernel/System/Console/Command/Maint/GenericInterface/Trigger.pm b/Kernel/System/Console/Command/Maint/GenericInterface/Trigger.pm index dd24325d21..922ca5e51d 100644 --- a/Kernel/System/Console/Command/Maint/GenericInterface/Trigger.pm +++ b/Kernel/System/Console/Command/Maint/GenericInterface/Trigger.pm @@ -34,14 +34,14 @@ sub Configure { $Self->Description('Triggers a given Invoker webservice.'); $Self->AddArgument( - Name => 'WebServiceName', + Name => 'webservicename', Description => "Select name of web service to be triggered.", Required => 1, HasValue => 1, ValueRegex => qr/./smx, ); $Self->AddArgument( - Name => 'Invoker', + Name => 'invoker', Description => "Select Invoker to be triggered.", Required => 1, HasValue => 1, @@ -54,21 +54,41 @@ sub Configure { sub PreRun { my ( $Self, %Param ) = @_; - my $Invoker = $Self->GetArgument('Invoker'); - my $WebserviceName = $Self->GetArgument('WebServiceName'); + my $Invoker = $Self->GetArgument('invoker'); + my $WebserviceName = $Self->GetArgument('webservicename'); # Check if all requirements are met (web service exists and has needed method). - my $Webservice - = $Kernel::OM->Get('Kernel::System::GenericInterface::Webservice')->WebserviceGet( Name => $WebserviceName ); - die "Required web service '$WebserviceName' does not exist!\n" if !IsHashRefWithData($Webservice); - die "Required web service '$WebserviceName' is invalid!\n" if $Webservice->{ValidID} ne 1; - - my $InvokerControllerMapping - = $Webservice->{Config}->{Requester}->{Transport}->{Config}->{InvokerControllerMapping}; - die "Web service '$WebserviceName' does not contain required REST controller mapping!\n" - if !IsHashRefWithData($InvokerControllerMapping); - die "Web service '$WebserviceName' does not contain the Invoker '$Invoker'!" - if !IsHashRefWithData( $InvokerControllerMapping->{$Invoker} ); + my $Webservice = $Kernel::OM->Get('Kernel::System::GenericInterface::Webservice')->WebserviceGet( + Name => $WebserviceName, + ); + + if ( !IsHashRefWithData($Webservice) ) { + $Self->Print( + "Required web service '$WebserviceName' does not exist!\n" + ); + return $Self->ExitCodeError(); + } + if ( $Webservice->{ValidID} ne '1' ) { + $Self->Print( + "Required web service '$WebserviceName' is invalid!\n" + ); + return $Self->ExitCodeError(); + } + + my $InvokerControllerMapping = $Webservice->{Config}{Requester}{Transport}{Config}{InvokerControllerMapping}; + + if ( !IsHashRefWithData($InvokerControllerMapping) ) { + $Self->Print( + "Web service '$WebserviceName' does not contain required REST controller mapping!\n" + ); + return $Self->ExitCodeError(); + } + if ( !IsHashRefWithData( $InvokerControllerMapping->{$Invoker} ) ) { + $Self->Print( + "Web service '$WebserviceName' does not contain the Invoker '$Invoker'!\n" + ); + return $Self->ExitCodeError(); + } # Remember data for task. $Self->{InvokerTaskData} = { @@ -83,7 +103,7 @@ sub PreRun { sub Run { my ( $Self, %Param ) = @_; - my $Invoker = $Self->GetArgument('Invoker'); + my $Invoker = $Self->GetArgument('invoker'); $Self->Print( "Triggering $Invoker for immediate (asynchronous) execution.\n" From 1f3f0a854ffc17dbec40aafcbc70318d334a2654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20H=C3=A4rter?= Date: Mon, 11 Nov 2024 17:22:15 +0100 Subject: [PATCH 14/21] Issue #3914: Renamed command. --- .../Maint/GenericInterface/{Trigger.pm => TriggerInvoker.pm} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Kernel/System/Console/Command/Maint/GenericInterface/{Trigger.pm => TriggerInvoker.pm} (99%) diff --git a/Kernel/System/Console/Command/Maint/GenericInterface/Trigger.pm b/Kernel/System/Console/Command/Maint/GenericInterface/TriggerInvoker.pm similarity index 99% rename from Kernel/System/Console/Command/Maint/GenericInterface/Trigger.pm rename to Kernel/System/Console/Command/Maint/GenericInterface/TriggerInvoker.pm index 922ca5e51d..f930ed2226 100644 --- a/Kernel/System/Console/Command/Maint/GenericInterface/Trigger.pm +++ b/Kernel/System/Console/Command/Maint/GenericInterface/TriggerInvoker.pm @@ -14,7 +14,7 @@ # along with this program. If not, see . # -- -package Kernel::System::Console::Command::Maint::GenericInterface::Trigger; +package Kernel::System::Console::Command::Maint::GenericInterface::TriggerInvoker; use strict; use warnings; From 4fa80733f249d49a5239bdf6aaa77590f0f89648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20H=C3=A4rter?= Date: Tue, 12 Nov 2024 08:42:50 +0100 Subject: [PATCH 15/21] Issue #3914: Renamend parameter. --- .../Console/Command/Maint/GenericInterface/TriggerInvoker.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Kernel/System/Console/Command/Maint/GenericInterface/TriggerInvoker.pm b/Kernel/System/Console/Command/Maint/GenericInterface/TriggerInvoker.pm index f930ed2226..61fc689a98 100644 --- a/Kernel/System/Console/Command/Maint/GenericInterface/TriggerInvoker.pm +++ b/Kernel/System/Console/Command/Maint/GenericInterface/TriggerInvoker.pm @@ -34,7 +34,7 @@ sub Configure { $Self->Description('Triggers a given Invoker webservice.'); $Self->AddArgument( - Name => 'webservicename', + Name => 'webservice', Description => "Select name of web service to be triggered.", Required => 1, HasValue => 1, @@ -55,7 +55,7 @@ sub PreRun { my ( $Self, %Param ) = @_; my $Invoker = $Self->GetArgument('invoker'); - my $WebserviceName = $Self->GetArgument('webservicename'); + my $WebserviceName = $Self->GetArgument('webservice'); # Check if all requirements are met (web service exists and has needed method). my $Webservice = $Kernel::OM->Get('Kernel::System::GenericInterface::Webservice')->WebserviceGet( From 7fe29a1d91357ec7ee7c725f107aa4c5a03837ec Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 12 Nov 2024 12:14:22 +0100 Subject: [PATCH 16/21] Issue #3765: convert test script to Test2::V0 use subtests for grouping related tests --- scripts/test/GenericInterface/Mapping/XSLT.t | 134 ++++++++++--------- 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/scripts/test/GenericInterface/Mapping/XSLT.t b/scripts/test/GenericInterface/Mapping/XSLT.t index 7f75704e42..31603a477a 100644 --- a/scripts/test/GenericInterface/Mapping/XSLT.t +++ b/scripts/test/GenericInterface/Mapping/XSLT.t @@ -14,6 +14,7 @@ # along with this program. If not, see . # -- +use v5.24; use strict; use warnings; use utf8; @@ -23,12 +24,10 @@ use utf8; # CPAN modules # OTOBO modules -use Kernel::System::UnitTest::RegisterDriver; # Set up $Kernel::OM and the test driver $Self +use Kernel::System::UnitTest::RegisterOM; # Set up $Kernel::OM use Kernel::GenericInterface::Debugger (); use Kernel::GenericInterface::Mapping (); -our $Self; - my $Home = $Kernel::OM->Get('Kernel::Config')->Get('Home'); my $DebuggerObject = Kernel::GenericInterface::Debugger->new( @@ -40,7 +39,7 @@ my $DebuggerObject = Kernel::GenericInterface::Debugger->new( CommunicationType => 'Provider', ); -my @MappingTests = ( +my @Tests = ( { Name => 'Test invalid xml', Config => { @@ -324,74 +323,81 @@ my @MappingTests = ( }, ); -TEST: -for my $Test (@MappingTests) { +# add some boolean tests - # create a mapping instance - my $MappingObject = Kernel::GenericInterface::Mapping->new( - DebuggerObject => $DebuggerObject, - MappingConfig => { - Type => 'XSLT', - Config => $Test->{Config}, - }, - ); - if ( $Test->{ConfigSuccess} ) { - $Self->Is( - ref $MappingObject, - 'Kernel::GenericInterface::Mapping', - $Test->{Name} . ' MappingObject was correctly instantiated', - ); - next TEST if ref $MappingObject ne 'Kernel::GenericInterface::Mapping'; - } - else { - $Self->IsNot( - ref $MappingObject, - 'Kernel::GenericInterface::Mapping', - $Test->{Name} . ' MappingObject was not correctly instantiated', +for my $Test (@Tests) { + + subtest "$Test->{Name}" => sub { + + # create a mapping instance + my $MappingObject = Kernel::GenericInterface::Mapping->new( + DebuggerObject => $DebuggerObject, + MappingConfig => { + Type => 'XSLT', + Config => $Test->{Config}, + }, ); - next TEST; - } + if ( $Test->{ConfigSuccess} ) { + is( + ref $MappingObject, + 'Kernel::GenericInterface::Mapping', + 'MappingObject was correctly instantiated', + ); - my $MappingResult = $MappingObject->Map( - Data => $Test->{Data}, - DataInclude => $Test->{DataInclude}, - ); + return unless ref $MappingObject eq 'Kernel::GenericInterface::Mapping'; + } + else { + isnt( + ref $MappingObject, + 'Kernel::GenericInterface::Mapping', + 'MappingObject was not correctly instantiated', + ); - # check if function return correct status - $Self->Is( - $MappingResult->{Success}, - $Test->{ResultSuccess}, - $Test->{Name} . ' (Success).', - ); + return; + } - # check if function return correct data - $Self->IsDeeply( - $MappingResult->{Data}, - $Test->{ResultData}, - $Test->{Name} . ' (Data Structure).', - ); + my $MappingResult = $MappingObject->Map( + Data => $Test->{Data}, + DataInclude => $Test->{DataInclude}, + ); - if ( !$Test->{ResultSuccess} ) { - $Self->True( - $MappingResult->{ErrorMessage}, - $Test->{Name} . ' error message found', + # check if function return correct status + is( + $MappingResult->{Success}, + $Test->{ResultSuccess}, + ( $Test->{ResultSuccess} ? 'Map() was successful' : 'Map() was not successful' ), ); - } - # instantiate another object - my $SecondMappingObject = Kernel::GenericInterface::Mapping->new( - DebuggerObject => $DebuggerObject, - MappingConfig => { - Type => 'XSLT', - Config => $Test->{Config}, - }, - ); + # check if function return correct data + is( + $MappingResult->{Data}, + $Test->{ResultData}, + 'Data Structure', + ); + + if ( !$Test->{ResultSuccess} ) { + diag $MappingResult->{ErrorMessage}; + ok( + $MappingResult->{ErrorMessage}, + 'error message found', + ); + } + + # instantiate another object + my $SecondMappingObject = Kernel::GenericInterface::Mapping->new( + DebuggerObject => $DebuggerObject, + MappingConfig => { + Type => 'XSLT', + Config => $Test->{Config}, + }, + ); - $Self->Is( - ref $SecondMappingObject, - 'Kernel::GenericInterface::Mapping', - $Test->{Name} . ' SecondMappingObject was correctly instantiated', - ); + is( + ref $SecondMappingObject, + 'Kernel::GenericInterface::Mapping', + 'SecondMappingObject was correctly instantiated', + ); + }; } -$Self->DoneTesting(); +done_testing; From 2a881f703f86cdd67bd4633ffaaaafb6799d4f22 Mon Sep 17 00:00:00 2001 From: bernhard Date: Wed, 13 Nov 2024 15:01:56 +0100 Subject: [PATCH 17/21] Issue #3765: add the method Kernel::System::JSON::IsBool() --- Kernel/System/JSON.pm | 20 +++++++++++++++++++- scripts/test/JSON.t | 22 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Kernel/System/JSON.pm b/Kernel/System/JSON.pm index f9273b979b..af6355f845 100644 --- a/Kernel/System/JSON.pm +++ b/Kernel/System/JSON.pm @@ -59,7 +59,7 @@ create a JSON object. Do not use it directly, instead use: sub new { my ($Type) = @_; - # allocate new hash for object + # allocate a new hash for object return bless {}, $Type; } @@ -264,4 +264,22 @@ sub ToBoolean { return $Scalar ? $Self->True : $Self->False; } +=head2 IsBool() + +Indicates whether the passed in variable is a boolean value. Specifically whether it is an +instance of C. Note that C is an alias for C. + + my $IsBool1 = $JSONObject->IsBool(1); # assigns undef + my $IsBool2 = $JSONObject->IsBool( $JSONObject->False); # assigns 1 + +In this case the returned JSON will be C. For true expressions we get C. + +=cut + +sub IsBool { + my ( $Self, $Scalar ) = @_; + + return Types::Serialiser::is_bool($Scalar); +} + 1; diff --git a/scripts/test/JSON.t b/scripts/test/JSON.t index 9a49212cad..89fadfe562 100644 --- a/scripts/test/JSON.t +++ b/scripts/test/JSON.t @@ -571,4 +571,26 @@ for my $Test (@DecodeTests) { is( $Thingy, $Test->{Result}, "decode: $Test->{Name}" ); } +# Testing IsBool() +subtest 'IsBool() for non-Booleans' => sub { + is( $JSONObject->IsBool(), undef, 'no argument' ); + is( $JSONObject->IsBool(undef), undef, 'explicit undef' ); + is( $JSONObject->IsBool(''), undef, 'empty string' ); + is( $JSONObject->IsBool(1), undef, 'integer 1' ); + is( $JSONObject->IsBool(2), undef, 'integer 2' ); + + # not sure why these return an empty string instead of undef + is( $JSONObject->IsBool('true'), '', 'string "true"' ); + is( $JSONObject->IsBool('⊨ - U+022A8 - TRUE'), '', 'a string' ); +}; + +subtest 'IsBool() for Booleans' => sub { + is( $JSONObject->IsBool( $JSONObject->True ), 1, 'true' ); + is( $JSONObject->IsBool( $JSONObject->False ), 1, 'false' ); + is( $JSONObject->IsBool( $JSONObject->ToBoolean(undef) ), 1, 'unded boolified' ); + is( $JSONObject->IsBool( $JSONObject->ToBoolean(0) ), 1, '0 boolified' ); + is( $JSONObject->IsBool( $JSONObject->ToBoolean(1) ), 1, '1 boolified' ); + is( $JSONObject->IsBool( $JSONObject->ToBoolean(' ') ), 1, 'single space boolified' ); +}; + done_testing; From 2146528b3426727750958494353e91ea071965ed Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 14 Nov 2024 15:03:06 +0100 Subject: [PATCH 18/21] Issue #3765: emit plain 0 and 1 for JSON boolean values --- Kernel/System/JSON.pm | 9 +++++++++ scripts/test/JSON.t | 22 +++++++++++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Kernel/System/JSON.pm b/Kernel/System/JSON.pm index af6355f845..7c069e0da3 100644 --- a/Kernel/System/JSON.pm +++ b/Kernel/System/JSON.pm @@ -185,6 +185,15 @@ sub Decode { # grudgingly accept data that is neither a hash- nor an array reference $JSONObject->allow_nonref(1); + # In OTOBO 10.0.x and OTOBO 10.1.x there is a tree walker that + # replaces the boolean values, that is instances of JSON::PP::Boolean, + # with the plain integer values 0 and 1. + # This behavior is reproduced with explicitly declaring + # what should be emitted for JSON booleans 'true' and 'false'. + # Note that when using Cpanel::JSON::XS, the attribute unblessed_bool can be used + # for the same purpose. + $JSONObject->boolean_values( 0, 1 ); + # Deserialize JSON and get a Perl data structure. # Use Try::Tiny as JSON::XS->decode() dies when providing a malformed JSON string. # In that case we want to return an empty list. diff --git a/scripts/test/JSON.t b/scripts/test/JSON.t index 89fadfe562..ce59a9c73a 100644 --- a/scripts/test/JSON.t +++ b/scripts/test/JSON.t @@ -498,16 +498,16 @@ my @DecodeTests = ( Name => 'JSON - complex structure' }, { - Result => 1, - InputDecode => - 'true', - Name => 'JSON - boolean true' + Result => 1, + VerifyScalar => 1, + InputDecode => 'true', + Name => 'JSON - boolean true' }, { - Result => 0, - InputDecode => - 'false', - Name => 'JSON - boolean false' + Result => 0, + VerifyScalar => 1, + InputDecode => 'false', + Name => 'JSON - boolean false' }, { Result => undef, @@ -567,8 +567,12 @@ for my $Test (@DecodeTests) { my $Thingy = $JSONObject->Decode( Data => $Test->{InputDecode}, ); + is( $Thingy, $Test->{Result}, "Decode: $Test->{Name}" ); - is( $Thingy, $Test->{Result}, "decode: $Test->{Name}" ); + # double check because 'is()' does not complain about instances JSON::PP::Boolean + if ( $Test->{VerifyScalar} ) { + is( ref $Thingy, '', "Decode: $Test->{Name}, result is not a reference" ); + } } # Testing IsBool() From ecbb3fda088629a4269f63690825f1a5fcb1c0b8 Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 14 Nov 2024 15:17:20 +0100 Subject: [PATCH 19/21] Issue #3765: forgot to load Test2::V0 in test script --- scripts/test/GenericInterface/Mapping/XSLT.t | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test/GenericInterface/Mapping/XSLT.t b/scripts/test/GenericInterface/Mapping/XSLT.t index 31603a477a..d69eaf7d3e 100644 --- a/scripts/test/GenericInterface/Mapping/XSLT.t +++ b/scripts/test/GenericInterface/Mapping/XSLT.t @@ -22,6 +22,7 @@ use utf8; # core modules # CPAN modules +use Test2::V0; # OTOBO modules use Kernel::System::UnitTest::RegisterOM; # Set up $Kernel::OM From 769d617c8fe6752880ed98aa082d423cbadf83fa Mon Sep 17 00:00:00 2001 From: bernhard Date: Fri, 15 Nov 2024 21:15:21 +0100 Subject: [PATCH 20/21] Issue #3765: add code comment about an unused hash --- Kernel/System/JSON.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kernel/System/JSON.pm b/Kernel/System/JSON.pm index 7c069e0da3..ec90c01fde 100644 --- a/Kernel/System/JSON.pm +++ b/Kernel/System/JSON.pm @@ -59,7 +59,7 @@ create a JSON object. Do not use it directly, instead use: sub new { my ($Type) = @_; - # allocate a new hash for object + # allocate a new hash for object, even though that hash is never used return bless {}, $Type; } From 83afe11983aa1a1acea29ad3137c802550e7b7b6 Mon Sep 17 00:00:00 2001 From: bernhard Date: Fri, 15 Nov 2024 21:18:43 +0100 Subject: [PATCH 21/21] Issue #3765: add tests with JSON as test data more precisely, with test input that is the result of parsing JSON --- scripts/test/GenericInterface/Mapping/XSLT.t | 168 ++++++++++++++++++- 1 file changed, 164 insertions(+), 4 deletions(-) diff --git a/scripts/test/GenericInterface/Mapping/XSLT.t b/scripts/test/GenericInterface/Mapping/XSLT.t index d69eaf7d3e..f98a12f1dc 100644 --- a/scripts/test/GenericInterface/Mapping/XSLT.t +++ b/scripts/test/GenericInterface/Mapping/XSLT.t @@ -54,7 +54,7 @@ my @Tests = ( ConfigSuccess => 1, }, { - Name => 'Test no xslt', + Name => 'Test no XSLT', Config => { Template => ' ', @@ -67,7 +67,7 @@ my @Tests = ( ConfigSuccess => 1, }, { - Name => 'Test invalid xslt', + Name => 'Test invalid XSLT', Config => { Template => ' @@ -324,11 +324,171 @@ my @Tests = ( }, ); -# add some boolean tests +# add some tests that take the input data from a JSON string +{ + my $JSONObject = $Kernel::OM->Get('Kernel::System::JSON'); + + # show that the mapping works with parsed JSON + push @Tests, + { + Name => 'ammend simple JSON array', + Config => { + Template => << 'END_TEMPLATE', + + + + + + + + ℘ - U+02118 - WEIERSTRASS ELLIPTIC FUNCTION: + + + + + + +END_TEMPLATE + }, + Data => $JSONObject->Decode( Data => <<'END_JSON' ), +{ + "Structure1": { + "Key2": "is ignored", + "Array1": [ "Element 🅐", "Element 🅑", "Element 🅒" ] + } +} +END_JSON + ResultData => { + WeierstrassArray => [ + '℘ - U+02118 - WEIERSTRASS ELLIPTIC FUNCTION:Element 🅐', + '℘ - U+02118 - WEIERSTRASS ELLIPTIC FUNCTION:Element 🅑', + '℘ - U+02118 - WEIERSTRASS ELLIPTIC FUNCTION:Element 🅒', + ], + }, + ResultSuccess => 1, + ConfigSuccess => 1, + }; + + # now with boolean checks, + # actually string tests as the node values have no type assigned + push @Tests, + { + Name => 'truthiness', + Config => { + Template => << 'END_TEMPLATE', + + + + + + + + + + ⊭ NOT TRUE: empty string: + + + ⊭ NOT TRUE: empty string: + + + ⊭ NOT TRUE: integer 0: + + + ⊨ TRUE: otherwise: + + + + , + + + + + + +END_TEMPLATE + }, + Data => $JSONObject->Decode( Data => <<'END_JSON' ), +{ + "Structure1": { + "Array1": [ "Element 🅐", "Element 🅑", "Element 🅒", 1, "", true, false ] + } +} +END_JSON + ResultData => { + 'TrueOrNotTrueArray' => [ + "\x{22a8} TRUE: otherwise:Element \x{1f150},Array1", + "\x{22a8} TRUE: otherwise:Element \x{1f151},Array1", + "\x{22a8} TRUE: otherwise:Element \x{1f152},Array1", + "\x{22a8} TRUE: otherwise:1,Array1", + "\x{22ad} NOT TRUE: empty string:,Array1", + "\x{22a8} TRUE: otherwise:1,Array1", + "\x{22ad} NOT TRUE: integer 0:0,Array1" + ] + }, + ResultSuccess => 1, + ConfigSuccess => 1, + }; + + # using a number variable + push @Tests, + { + Name => 'number variable', + Config => { + Template => << 'END_TEMPLATE', + + + + + + + + + + + + ⊨ TRUE: + + + ⊭ NOT TRUE: + + + + , + + + + + + +END_TEMPLATE + }, + Data => $JSONObject->Decode( Data => <<'END_JSON' ), +{ + "Structure1": { + "Array1": [ -1, 0, 1, "-1", "0", "1", false, true ] + } +} +END_JSON + ResultData => { + 'TrueOrNotTrueArray' => [ + "\x{22a8} TRUE:-1,99", + "\x{22ad} NOT TRUE:0,100", + "\x{22a8} TRUE:1,101", + "\x{22a8} TRUE:-1,99", + "\x{22ad} NOT TRUE:0,100", + "\x{22a8} TRUE:1,101", + "\x{22ad} NOT TRUE:0,100", + "\x{22a8} TRUE:1,101", + ] + }, + ResultSuccess => 1, + ConfigSuccess => 1, + }; +} for my $Test (@Tests) { - subtest "$Test->{Name}" => sub { + subtest $Test->{Name} => sub { # create a mapping instance my $MappingObject = Kernel::GenericInterface::Mapping->new(