# -- # Copyright (C) 2001-2019 OTRS AG, https://otrs.com/ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you # did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. # -- package Kernel::Modules::PublicFAQSearch; use strict; use warnings; use MIME::Base64 qw(); use Kernel::Language qw(Translatable); use Kernel::System::VariableCheck qw(:all); our $ObjectManagerDisabled = 1; sub new { my ( $Type, %Param ) = @_; # allocate new hash for object my $Self = {%Param}; bless( $Self, $Type ); # set UserID to root because in public interface there is no user $Self->{UserID} = 1; # get config for frontend $Self->{Config} = $Kernel::OM->Get('Kernel::Config')->Get("FAQ::Frontend::$Self->{Action}"); # get dynamic field config for frontend module $Self->{DynamicFieldFilter} = $Self->{Config}->{DynamicField}; # get the dynamic fields for FAQ object $Self->{DynamicField} = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( Valid => 1, ObjectType => 'FAQ', FieldFilter => $Self->{DynamicFieldFilter} || {}, ); # reduce the dynamic fields to only the ones that are designed for customer interface my @CustomerDynamicFields; DYNAMICFIELDCONFIG: for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { next DYNAMICFIELDCONFIG if !IsHashRefWithData($DynamicFieldConfig); my $IsCustomerInterfaceCapable = $Kernel::OM->Get('Kernel::System::DynamicField::Backend')->HasBehavior( DynamicFieldConfig => $DynamicFieldConfig, Behavior => 'IsCustomerInterfaceCapable', ); next DYNAMICFIELDCONFIG if !$IsCustomerInterfaceCapable; push @CustomerDynamicFields, $DynamicFieldConfig; } $Self->{DynamicField} = \@CustomerDynamicFields; return $Self; } sub Run { my ( $Self, %Param ) = @_; my $Output; # get config from constructor my $Config = $Self->{Config}; my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); # get config data my $StartHit = int( $ParamObject->GetParam( Param => 'StartHit' ) || 1 ); my $SearchLimit = $Config->{SearchLimit} || 200; my $SearchPageShown = $Config->{SearchPageShown} || 40; my $SortBy = $ParamObject->GetParam( Param => 'SortBy' ) || $Config->{'SortBy::Default'} || 'FAQID'; my $OrderBy = $ParamObject->GetParam( Param => 'Order' ) || $Config->{'Order::Default'} || 'Down'; my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); # build output for open search description by FAQ number if ( $Self->{Subaction} eq 'OpenSearchDescriptionFAQNumber' ) { my $Output = $LayoutObject->Output( TemplateFile => 'PublicFAQSearchOpenSearchDescriptionFAQNumber', Data => \%Param, ); return $LayoutObject->Attachment( Filename => 'OpenSearchDescriptionFAQNumber.xml', ContentType => 'application/opensearchdescription+xml', Content => $Output, Type => 'inline', ); } # build output for open search description by full-text if ( $Self->{Subaction} eq 'OpenSearchDescriptionFulltext' ) { my $Output = $LayoutObject->Output( TemplateFile => 'PublicFAQSearchOpenSearchDescriptionFullText', Data => \%Param, ); return $LayoutObject->Attachment( Filename => 'OpenSearchDescriptionFulltext.xml', ContentType => 'application/opensearchdescription+xml', Content => $Output, Type => 'inline', ); } # remember exclude attributes my @Excludes = $ParamObject->GetArray( Param => 'Exclude' ); my %GetParam; # get single params for my $ParamName ( qw(Number Title Keyword Fulltext ResultForm TimeSearchType VoteSearch VoteSearchType VoteSearchOption RateSearch RateSearchType RateSearchOption ItemCreateTimePointFormat ItemCreateTimePoint ItemCreateTimePointStart ItemCreateTimeStart ItemCreateTimeStartDay ItemCreateTimeStartMonth ItemCreateTimeStartYear ItemCreateTimeStop ItemCreateTimeStopDay ItemCreateTimeStopMonth ItemCreateTimeStopYear ) ) { # get search string params (get submitted params) $GetParam{$ParamName} = $ParamObject->GetParam( Param => $ParamName ); # remove whitespace on the start and end if ( $GetParam{$ParamName} ) { $GetParam{$ParamName} =~ s{ \A \s+ }{}xms; $GetParam{$ParamName} =~ s{ \s+ \z }{}xms; } # store non empty parameters on a local profile if ( $GetParam{$ParamName} ) { $Self->{Profile} .= "$ParamName=$GetParam{$ParamName};"; } } # get back link $GetParam{SearchBackLink} = $ParamObject->GetParam( Param => 'SearchBackLink' ) || ''; if ( $GetParam{SearchBackLink} ) { $GetParam{SearchBackLink} = MIME::Base64::decode_base64( $GetParam{SearchBackLink} ); } # show back link $LayoutObject->Block( Name => 'Back', Data => { %GetParam, }, ); my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); # get array params for my $ParamName (qw(CategoryIDs LanguageIDs )) { # get search array params (get submitted params) my @Array = $ParamObject->GetArray( Param => $ParamName ); if (@Array) { $GetParam{$ParamName} = \@Array; # store parameters on a local profile for my $Element (@Array) { $Self->{Profile} .= $ParamName . '=' . $Element . ';'; } } } # get Dynamic fields from param object DYNAMICFIELDCONFIG: for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { next DYNAMICFIELDCONFIG if !IsHashRefWithData($DynamicFieldConfig); # get search field preferences my $SearchFieldPreferences = $DynamicFieldBackendObject->SearchFieldPreferences( DynamicFieldConfig => $DynamicFieldConfig, ); next DYNAMICFIELDCONFIG if !IsArrayRefWithData($SearchFieldPreferences); PREFERENCE: for my $Preference ( @{$SearchFieldPreferences} ) { # extract the dynamic field value from the web request my $DynamicFieldValue = $DynamicFieldBackendObject->SearchFieldValueGet( DynamicFieldConfig => $DynamicFieldConfig, ParamObject => $ParamObject, ReturnProfileStructure => 1, LayoutObject => $LayoutObject, Type => $Preference->{Type}, ); # set the complete value structure in GetParam to store it later in the search # profile if ( IsHashRefWithData($DynamicFieldValue) ) { %GetParam = ( %GetParam, %{$DynamicFieldValue} ); } # add dynamic fields to profile to include them in the back-link KEYITEM: for my $KeyItem ( sort keys %{$DynamicFieldValue} ) { # convert scalar values to array to use same code base if ( defined $DynamicFieldValue->{$KeyItem} && ref $DynamicFieldValue->{$KeyItem} eq '' ) { $DynamicFieldValue->{$KeyItem} = [ $DynamicFieldValue->{$KeyItem} ]; } next KEYITEM if !IsArrayRefWithData( $DynamicFieldValue->{$KeyItem} ); # concatenate dynamic fields values into the profile VALUEITEM: for my $ValueItem ( @{ $DynamicFieldValue->{$KeyItem} } ) { next VALUEITEM if !$ValueItem; $Self->{Profile} .= "$KeyItem=$ValueItem;"; } } } } # check if item need to get excluded for my $Exclude (@Excludes) { if ( $GetParam{$Exclude} ) { delete $GetParam{$Exclude}; } } # get vote option if ( !$GetParam{VoteSearchOption} ) { $GetParam{'VoteSearchOption::None'} = 'checked="checked"'; } elsif ( $GetParam{VoteSearchOption} eq 'VotePoint' ) { $GetParam{'VoteSearchOption::VotePoint'} = 'checked="checked"'; } # get rate option if ( !$GetParam{RateSearchOption} ) { $GetParam{'RateSearchOption::None'} = 'checked="checked"'; } elsif ( $GetParam{RateSearchOption} eq 'RatePoint' ) { $GetParam{'RateSearchOption::RatePoint'} = 'checked="checked"'; } # get time option if ( !$GetParam{TimeSearchType} ) { $GetParam{'TimeSearchType::None'} = 'checked="checked"'; } elsif ( $GetParam{TimeSearchType} eq 'TimePoint' ) { $GetParam{'TimeSearchType::TimePoint'} = 'checked="checked"'; } elsif ( $GetParam{TimeSearchType} eq 'TimeSlot' ) { $GetParam{'TimeSearchType::TimeSlot'} = 'checked="checked"'; } # set result form ENV if ( !$GetParam{ResultForm} ) { $GetParam{ResultForm} = ''; } if ( $GetParam{ResultForm} eq 'Print' ) { $SearchPageShown = $SearchLimit; } # check request if ( $Self->{Subaction} eq 'OpenSearchDescription' ) { my $Output = $LayoutObject->Output( TemplateFile => 'PublicFAQSearchOpenSearchDescription', Data => {%Param}, ); return $LayoutObject->Attachment( Filename => 'OpenSearchDescription.xml', ContentType => 'text/xml', Content => $Output, Type => 'inline', ); } # show result page if ( $Self->{Subaction} eq 'Search' && !$Self->{EraseTemplate} ) { # dynamic fields search parameters for FAQ search my %DynamicFieldSearchParameters; my %DynamicFieldSearchDisplay; DYNAMICFIELDCONFIG: for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { next DYNAMICFIELDCONFIG if !IsHashRefWithData($DynamicFieldConfig); # get search field preferences my $SearchFieldPreferences = $DynamicFieldBackendObject->SearchFieldPreferences( DynamicFieldConfig => $DynamicFieldConfig, ); next DYNAMICFIELDCONFIG if !IsArrayRefWithData($SearchFieldPreferences); PREFERENCE: for my $Preference ( @{$SearchFieldPreferences} ) { my $DynamicFieldValue = $DynamicFieldBackendObject->SearchFieldValueGet( DynamicFieldConfig => $DynamicFieldConfig, ParamObject => $ParamObject, Type => $Preference->{Type}, ReturnProfileStructure => 1, ); # set the complete value structure in %DynamicFieldValues to discard those where the # value will not be possible to get next PREFERENCE if !IsHashRefWithData($DynamicFieldValue); # extract the dynamic field value from the profile my $SearchParameter = $DynamicFieldBackendObject->SearchFieldParameterBuild( DynamicFieldConfig => $DynamicFieldConfig, Profile => \%GetParam, LayoutObject => $LayoutObject, Type => $Preference->{Type}, ); # set search parameter if ( defined $SearchParameter ) { $DynamicFieldSearchParameters{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = $SearchParameter->{Parameter}; # set value to display $DynamicFieldSearchDisplay{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = $SearchParameter->{Display}; } } } # prepare full-text search if ( $GetParam{Fulltext} ) { $GetParam{ContentSearch} = 'OR'; $GetParam{What} = $GetParam{Fulltext}; } # prepare votes search if ( IsNumber( $GetParam{VoteSearch} ) && $GetParam{VoteSearchOption} ) { $GetParam{Votes} = { $GetParam{VoteSearchType} => $GetParam{VoteSearch} }; } # prepare rate search if ( IsNumber( $GetParam{RateSearch} ) && $GetParam{RateSearchOption} ) { $GetParam{Rate} = { $GetParam{RateSearchType} => $GetParam{RateSearch} }; } my %TimeMap = ( ItemCreate => 'Time', ); for my $TimeType ( sort keys %TimeMap ) { # get create time settings if ( !$GetParam{ $TimeMap{$TimeType} . 'SearchType' } ) { # do nothing with time stuff } elsif ( $GetParam{ $TimeMap{$TimeType} . 'SearchType' } eq 'TimeSlot' ) { for my $Key (qw(Month Day)) { $GetParam{ $TimeType . 'TimeStart' . $Key } = sprintf( "%02d", $GetParam{ $TimeType . 'TimeStart' . $Key } ); $GetParam{ $TimeType . 'TimeStop' . $Key } = sprintf( "%02d", $GetParam{ $TimeType . 'TimeStop' . $Key } ); } if ( $GetParam{ $TimeType . 'TimeStartDay' } && $GetParam{ $TimeType . 'TimeStartMonth' } && $GetParam{ $TimeType . 'TimeStartYear' } ) { $GetParam{ $TimeType . 'TimeNewerDate' } = $GetParam{ $TimeType . 'TimeStartYear' } . '-' . $GetParam{ $TimeType . 'TimeStartMonth' } . '-' . $GetParam{ $TimeType . 'TimeStartDay' } . ' 00:00:00'; } if ( $GetParam{ $TimeType . 'TimeStopDay' } && $GetParam{ $TimeType . 'TimeStopMonth' } && $GetParam{ $TimeType . 'TimeStopYear' } ) { $GetParam{ $TimeType . 'TimeOlderDate' } = $GetParam{ $TimeType . 'TimeStopYear' } . '-' . $GetParam{ $TimeType . 'TimeStopMonth' } . '-' . $GetParam{ $TimeType . 'TimeStopDay' } . ' 23:59:59'; } } elsif ( $GetParam{ $TimeMap{$TimeType} . 'SearchType' } eq 'TimePoint' ) { if ( $GetParam{ $TimeType . 'TimePoint' } && $GetParam{ $TimeType . 'TimePointStart' } && $GetParam{ $TimeType . 'TimePointFormat' } ) { my $Time = 0; if ( $GetParam{ $TimeType . 'TimePointFormat' } eq 'minute' ) { $Time = $GetParam{ $TimeType . 'TimePoint' }; } elsif ( $GetParam{ $TimeType . 'TimePointFormat' } eq 'hour' ) { $Time = $GetParam{ $TimeType . 'TimePoint' } * 60; } elsif ( $GetParam{ $TimeType . 'TimePointFormat' } eq 'day' ) { $Time = $GetParam{ $TimeType . 'TimePoint' } * 60 * 24; } elsif ( $GetParam{ $TimeType . 'TimePointFormat' } eq 'week' ) { $Time = $GetParam{ $TimeType . 'TimePoint' } * 60 * 24 * 7; } elsif ( $GetParam{ $TimeType . 'TimePointFormat' } eq 'month' ) { $Time = $GetParam{ $TimeType . 'TimePoint' } * 60 * 24 * 30; } elsif ( $GetParam{ $TimeType . 'TimePointFormat' } eq 'year' ) { $Time = $GetParam{ $TimeType . 'TimePoint' } * 60 * 24 * 365; } if ( $GetParam{ $TimeType . 'TimePointStart' } eq 'Before' ) { # more than ... ago $GetParam{ $TimeType . 'TimeOlderMinutes' } = $Time; } elsif ( $GetParam{ $TimeType . 'TimePointStart' } eq 'Next' ) { # within next $GetParam{ $TimeType . 'TimeNewerMinutes' } = 0; $GetParam{ $TimeType . 'TimeOlderMinutes' } = -$Time; } else { # within last ... $GetParam{ $TimeType . 'TimeOlderMinutes' } = 0; $GetParam{ $TimeType . 'TimeNewerMinutes' } = $Time; } } } } my $FAQObject = $Kernel::OM->Get('Kernel::System::FAQ'); my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); # set default interface settings my $Interface = $FAQObject->StateTypeGet( Name => 'public', UserID => $Self->{UserID}, ); my $InterfaceStates = $FAQObject->StateTypeList( Types => $ConfigObject->Get('FAQ::Public::StateTypes'), UserID => $Self->{UserID}, ); # perform FAQ search my @ViewableItemIDs = $FAQObject->FAQSearch( OrderBy => [$SortBy], OrderByDirection => [$OrderBy], Limit => $SearchLimit, UserID => $Self->{UserID}, States => $InterfaceStates, Interface => $Interface, ContentSearchPrefix => '*', ContentSearchSuffix => '*', %GetParam, %DynamicFieldSearchParameters, ); my $MultiLanguage = $ConfigObject->Get('FAQ::MultiLanguage'); my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField'); # CSV output if ( $GetParam{ResultForm} eq 'CSV' ) { my @TmpCSVHead; my @CSVHead; my @CSVData; # get the FAQ dynamic fields for CSV display my $CSVDynamicField = $DynamicFieldObject->DynamicFieldListGet( Valid => 1, ObjectType => 'FAQ', FieldFilter => $Config->{SearchCSVDynamicField} || {}, ); # reduce the dynamic fields to only the ones that are designed for customer interface my @CSVCustomerDynamicFields; DYNAMICFIELDCONFIG: for my $DynamicFieldConfig ( @{$CSVDynamicField} ) { next DYNAMICFIELDCONFIG if !IsHashRefWithData($DynamicFieldConfig); my $IsCustomerInterfaceCapable = $DynamicFieldBackendObject->HasBehavior( DynamicFieldConfig => $DynamicFieldConfig, Behavior => 'IsCustomerInterfaceCapable', ); next DYNAMICFIELDCONFIG if !$IsCustomerInterfaceCapable; push @CSVCustomerDynamicFields, $DynamicFieldConfig; } $CSVDynamicField = \@CSVCustomerDynamicFields; for my $ItemID (@ViewableItemIDs) { my %FAQData = $FAQObject->FAQGet( ItemID => $ItemID, ItemFields => 0, DynamicFields => 1, UserID => $Self->{UserID}, ); # get info for CSV output my %CSVInfo = (%FAQData); $CSVInfo{Changed} = $LayoutObject->{LanguageObject}->FormatTimeString( $FAQData{Changed}, 'DateFormatLong', ); # CSV quote if ( !@CSVHead ) { @TmpCSVHead = qw( FAQNumber Title Category); @CSVHead = qw( FAQNumber Title Category); # insert language header if ($MultiLanguage) { push @TmpCSVHead, 'Language'; push @CSVHead, 'Language'; } push @CSVHead, 'Changed'; push @TmpCSVHead, 'Changed'; # include the selected dynamic fields on CVS results DYNAMICFIELD: for my $DynamicFieldConfig ( @{$CSVDynamicField} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; next DYNAMICFIELD if $DynamicFieldConfig->{Name} eq ''; push @TmpCSVHead, 'DynamicField_' . $DynamicFieldConfig->{Name}; push @CSVHead, $DynamicFieldConfig->{Label}; } } my @Data; for my $Header (@CSVHead) { # check if header is a dynamic field and get the value from dynamic field # backend if ( $Header =~ m{\A DynamicField_ ( [a-zA-Z\d]+ ) \z}xms ) { # loop over the dynamic fields configured for CSV output DYNAMICFIELD: for my $DynamicFieldConfig ( @{$CSVDynamicField} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; # skip all fields that does not match with current field name ($1) # with out the 'DynamicField_' prefix next DYNAMICFIELD if $DynamicFieldConfig->{Name} ne $1; # get the value as for print (to correctly display) my $ValueStrg = $DynamicFieldBackendObject->DisplayValueRender( DynamicFieldConfig => $DynamicFieldConfig, Value => $CSVInfo{$Header}, HTMLOutput => 0, LayoutObject => $LayoutObject, ); push @Data, $ValueStrg->{Value}; # terminate the DYNAMICFIELD loop last DYNAMICFIELD; } } # otherwise retrieve data from FAQ item else { if ( $Header eq 'FAQNumber' ) { push @Data, $CSVInfo{'Number'}; } elsif ( $Header eq 'Category' ) { push @Data, $CSVInfo{'CategoryName'}; } else { push @Data, $CSVInfo{$Header}; } } } push @CSVData, \@Data; } # CSV quote # translate non existing header may result in a garbage file if ( !@CSVHead ) { @TmpCSVHead = qw(FAQNumber Title Category); @CSVHead = qw(FAQNumber Title Category); # insert language header if ($MultiLanguage) { push @TmpCSVHead, 'Language'; push @CSVHead, 'Language'; } push @TmpCSVHead, 'Changed'; push @CSVHead, 'Changed'; # include the selected dynamic fields on CSV results DYNAMICFIELD: for my $DynamicFieldConfig ( @{$CSVDynamicField} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; next DYNAMICFIELD if $DynamicFieldConfig->{Name} eq ''; push @TmpCSVHead, 'DynamicField_' . $DynamicFieldConfig->{Name}; push @CSVHead, $DynamicFieldConfig->{Label}; } } # translate headers for my $Header (@CSVHead) { # replace FAQNumber header with the current FAQHook from config if ( $Header eq 'FAQNumber' ) { $Header = $ConfigObject->Get('FAQ::FAQHook'); } else { $Header = $LayoutObject->{LanguageObject}->Translate($Header); } } # assemble CSV data my $CSV = $Kernel::OM->Get('Kernel::System::CSV')->Array2CSV( Head => \@CSVHead, Data => \@CSVData, Separator => $Self->{UserCSVSeparator}, ); # Return CSV to download. my $CSVFile = 'FAQ_search'; my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); my $DateTime = $DateTimeObject->Get(); my $Y = $DateTime->{Year}; my $M = sprintf( "%02d", $DateTime->{Month} ); my $D = sprintf( "%02d", $DateTime->{Day} ); my $h = sprintf( "%02d", $DateTime->{Hour} ); my $m = sprintf( "%02d", $DateTime->{Minute} ); return $LayoutObject->Attachment( Filename => $CSVFile . "_" . "$Y-$M-$D" . "_" . "$h-$m.csv", ContentType => "text/csv; charset=" . $LayoutObject->{UserCharset}, Content => $CSV, ); } elsif ( $GetParam{ResultForm} eq 'Print' ) { my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF'); my @PDFData; for my $ItemID (@ViewableItemIDs) { my %FAQData = $FAQObject->FAQGet( ItemID => $ItemID, ItemFields => 0, UserID => $Self->{UserID}, ); # add table block $LayoutObject->Block( Name => 'Record', Data => {%FAQData}, ); # add language data if ($MultiLanguage) { $LayoutObject->Block( Name => 'RecordLanguage', Data => {%FAQData}, ); } # set change date to long format my $Changed = $LayoutObject->{LanguageObject}->FormatTimeString( $FAQData{Changed}, 'DateFormatLong', ); # create PDF Rows my @PDFRow; push @PDFRow, $FAQData{Number}; push @PDFRow, $FAQData{Title}; push @PDFRow, $FAQData{CategoryName}; # create language row if ($MultiLanguage) { push @PDFRow, $FAQData{Language}; } push @PDFRow, $FAQData{State}; push @PDFRow, $Changed; push @PDFData, \@PDFRow; } # PDF Output my $Title = $LayoutObject->{LanguageObject}->Translate('FAQ') . ' ' . $LayoutObject->{LanguageObject}->Translate('Search'); my $Page = $LayoutObject->{LanguageObject}->Translate('Page'); my $Time = $LayoutObject->{Time}; # get maximum number of pages my $MaxPages = $ConfigObject->Get('PDF::MaxPages'); if ( !$MaxPages || $MaxPages < 1 || $MaxPages > 1000 ) { $MaxPages = 100; } my $CellData; # output 'No Result', if no content was given if (@PDFData) { # create the header $CellData->[0]->[0]->{Content} = $ConfigObject->Get('FAQ::FAQHook'); $CellData->[0]->[0]->{Font} = 'ProportionalBold'; $CellData->[0]->[1]->{Content} = $LayoutObject->{LanguageObject}->Translate('Title'); $CellData->[0]->[1]->{Font} = 'ProportionalBold'; $CellData->[0]->[2]->{Content} = $LayoutObject->{LanguageObject}->Translate('Category'); $CellData->[0]->[2]->{Font} = 'ProportionalBold'; # store the correct header index my $NextHeaderIndex = 3; # add language header if ($MultiLanguage) { $CellData->[0]->[3]->{Content} = $LayoutObject->{LanguageObject}->Translate('Language'); $CellData->[0]->[3]->{Font} = 'ProportionalBold'; $NextHeaderIndex = 4; } $CellData->[0]->[$NextHeaderIndex]->{Content} = $LayoutObject->{LanguageObject}->Translate('State'); $CellData->[0]->[$NextHeaderIndex]->{Font} = 'ProportionalBold'; $CellData->[0]->[ $NextHeaderIndex + 1 ]->{Content} = $LayoutObject->{LanguageObject}->Translate('Changed'); $CellData->[0]->[ $NextHeaderIndex + 1 ]->{Font} = 'ProportionalBold'; # create the content array my $CounterRow = 1; for my $Row (@PDFData) { my $CounterColumn = 0; for my $Content ( @{$Row} ) { $CellData->[$CounterRow]->[$CounterColumn]->{Content} = $Content; $CounterColumn++; } $CounterRow++; } } else { $CellData->[0]->[0]->{Content} = $LayoutObject->{LanguageObject}->Translate('No Result!'); } # page params my %PageParam; $PageParam{PageOrientation} = 'landscape'; $PageParam{MarginTop} = 30; $PageParam{MarginRight} = 40; $PageParam{MarginBottom} = 40; $PageParam{MarginLeft} = 40; $PageParam{HeaderRight} = $Title; # table params my %TableParam; $TableParam{CellData} = $CellData; $TableParam{Type} = 'Cut'; $TableParam{FontSize} = 6; $TableParam{Border} = 0; $TableParam{BackgroundColorEven} = '#DDDDDD'; $TableParam{Padding} = 1; $TableParam{PaddingTop} = 3; $TableParam{PaddingBottom} = 3; # create new PDF document $PDFObject->DocumentNew( Title => $ConfigObject->Get('Product') . ': ' . $Title, Encode => $LayoutObject->{UserCharset}, ); # start table output $PDFObject->PageNew( %PageParam, FooterRight => $Page . ' 1', ); $PDFObject->PositionSet( Move => 'relativ', Y => -6, ); # output title $PDFObject->Text( Text => $Title, FontSize => 13, ); $PDFObject->PositionSet( Move => 'relativ', Y => -6, ); # output "printed by" $PDFObject->Text( Text => $Time, FontSize => 9, ); $PDFObject->PositionSet( Move => 'relativ', Y => -14, ); PAGE: for my $PageCount ( 2 .. $MaxPages ) { # output table (or a fragment of it) %TableParam = $PDFObject->Table( %TableParam, ); # stop output or another page if ( $TableParam{State} ) { last PAGE; } else { $PDFObject->PageNew( %PageParam, FooterRight => $Page . ' ' . $PageCount, ); } } # Return the PDF document. my $Filename = 'FAQ_search'; my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); my $DateTime = $DateTimeObject->Get(); my $Y = $DateTime->{Year}; my $M = sprintf( "%02d", $DateTime->{Month} ); my $D = sprintf( "%02d", $DateTime->{Day} ); my $h = sprintf( "%02d", $DateTime->{Hour} ); my $m = sprintf( "%02d", $DateTime->{Minute} ); my $PDFString = $PDFObject->DocumentOutput(); return $LayoutObject->Attachment( Filename => $Filename . "_" . "$Y-$M-$D" . "_" . "$h-$m.pdf", ContentType => "application/pdf", Content => $PDFString, Type => 'inline', ); } my $Counter = 0; # if there are results to show if (@ViewableItemIDs) { # create back link for FAQ Zoom screen my $ZoomBackLink = "Action=PublicFAQSearch;Subaction=Search;" . $Self->{Profile} . "SortBy=$SortBy;Order=$OrderBy;StartHit=$StartHit"; # encode back link to Base64 for easy HTML transport $ZoomBackLink = MIME::Base64::encode_base64($ZoomBackLink); my $OverviewConfig = $ConfigObject->Get("FAQ::Frontend::PublicFAQOverview"); # get the ticket dynamic fields for overview display my $OverviewDynamicField = $DynamicFieldObject->DynamicFieldListGet( Valid => 1, ObjectType => 'FAQ', FieldFilter => $OverviewConfig->{DynamicField} || {}, ); # reduce the dynamic fields to only the ones that are designed for customer interface my @OverviewCustomerDynamicFields; DYNAMICFIELD: for my $DynamicFieldConfig ( @{$OverviewDynamicField} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); my $IsCustomerInterfaceCapable = $DynamicFieldBackendObject->HasBehavior( DynamicFieldConfig => $DynamicFieldConfig, Behavior => 'IsCustomerInterfaceCapable', ); next DYNAMICFIELD if !$IsCustomerInterfaceCapable; push @OverviewCustomerDynamicFields, $DynamicFieldConfig; } $OverviewDynamicField = \@OverviewCustomerDynamicFields; # Dynamic fields table headers DYNAMICFIELD: for my $DynamicFieldConfig ( @{$OverviewDynamicField} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); my $Label = $DynamicFieldConfig->{Label}; # get field sortable condition my $IsSortable = $DynamicFieldBackendObject->HasBehavior( DynamicFieldConfig => $DynamicFieldConfig, Behavior => 'IsSortable', ); if ($IsSortable) { my $CSS = ''; my $Order = 'Down'; if ( $SortBy && ( $SortBy eq ( 'DynamicField_' . $DynamicFieldConfig->{Name} ) ) ) { if ( $Self->{Order} && ( $Self->{Order} eq 'Up' ) ) { $Order = 'Down'; $CSS .= ' SortAscending'; } else { $Order = 'Up'; $CSS .= ' SortDescending'; } } $LayoutObject->Block( Name => 'HeaderDynamicField', Data => { %Param, CSS => $CSS, }, ); $LayoutObject->Block( Name => 'HeaderDynamicFieldSortable', Data => { %Param, Order => $Order, Label => $Label, DynamicFieldName => $DynamicFieldConfig->{Name}, }, ); } else { $LayoutObject->Block( Name => 'HeaderDynamicField', Data => { %Param, }, ); $LayoutObject->Block( Name => 'HeaderDynamicFieldNotSortable', Data => { %Param, Label => $Label, }, ); } } for my $ItemID (@ViewableItemIDs) { $Counter++; # build search result if ( $Counter >= $StartHit && $Counter < ( $SearchPageShown + $StartHit ) ) { # get FAQ data details my %FAQData = $FAQObject->FAQGet( ItemID => $ItemID, ItemFields => 0, DynamicFields => 1, UserID => $Self->{UserID}, ); $FAQData{CleanTitle} = $FAQObject->FAQArticleTitleClean( Title => $FAQData{Title}, Size => $Config->{TitleSize}, ); # add blocks to template $LayoutObject->Block( Name => 'Record', Data => { %FAQData, ZoomBackLink => $ZoomBackLink, }, ); # add language data if ($MultiLanguage) { $LayoutObject->Block( Name => 'RecordLanguage', Data => {%FAQData}, ); } # Dynamic fields DYNAMICFIELD: for my $DynamicFieldConfig ( @{$OverviewDynamicField} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); # get field value my $ValueStrg = $DynamicFieldBackendObject->DisplayValueRender( DynamicFieldConfig => $DynamicFieldConfig, Value => $FAQData{ 'DynamicField_' . $DynamicFieldConfig->{Name} }, ValueMaxChars => 20, LayoutObject => $LayoutObject, ); $LayoutObject->Block( Name => 'RecordDynamicField', Data => { Value => $ValueStrg->{Value}, Title => $ValueStrg->{Title}, }, ); } } } } # otherwise show a no data found message else { $LayoutObject->Block( Name => 'NoDataFoundMsg' ); } # create a lookup table for attribute settings my %AttributeMap = ( Number => { Name => $ConfigObject->Get('FAQ::FAQHook'), Translatable => 0, }, Title => { Name => 'Title', Translatable => 1, }, Keyword => { Name => 'Keyword', Translatable => 1, }, Fulltext => { Name => 'Fulltext', Translatable => 1, }, CategoryIDs => { Name => 'Category', Translatable => 1, }, LanguageIDs => { Name => 'Language', Translatable => 1, }, TimeSearchType => { Name => 'Create Time', Translatable => 1, }, VoteSearchType => { Name => 'Votes', Translatable => 1, }, RateSearchType => { Name => 'Rate', Translatable => 1, }, ); # print each attribute in search results area. ATTRIBUTE: for my $Attribute ( sort keys %AttributeMap ) { # check if the attribute was defined by the user if ( $GetParam{$Attribute} ) { # set attribute name and translate it if applies my $AttributeName = $AttributeMap{$Attribute}->{Name}; if ( $AttributeMap{$Attribute}->{Translatable} ) { $AttributeName = $LayoutObject->{LanguageObject}->Translate($AttributeName); } my $AttributeValue; # check if the values is an array to parse each value if ( ref $GetParam{$Attribute} eq 'ARRAY' ) { # Category attribute if ( $Attribute eq 'CategoryIDs' ) { # get the long name for all public categories my $CategoryList = $FAQObject->GetPublicCategoriesLongNames( Type => 'rw', UserID => 1, ); # convert each category id to category long name my @CategoryNames; CATEGORYID: for my $CatedoryID ( @{ $GetParam{$Attribute} } ) { next CATEGORYID if !$CategoryList->{$CatedoryID}; push @CategoryNames, $CategoryList->{$CatedoryID}; } # create a string with all selected category names $AttributeValue = join( " + ", @CategoryNames ); } # LanguageIDs elsif ( $Attribute eq 'LanguageIDs' ) { # convert each language id to language name my @LanguageNames; LANGUAGEID: for my $LanguageID ( @{ $GetParam{$Attribute} } ) { my $LanguageName = $FAQObject->LanguageLookup( LanguageID => $LanguageID, ); next LANGUAGEID if !$LanguageName; push @LanguageNames, $LanguageName; } # create a string with all selected language names $AttributeValue = join( " + ", @LanguageNames ); } } # otherwise is an scalar and can be set directly else { $AttributeValue = $GetParam{$Attribute}; } if ( $Attribute eq 'TimeSearchType' ) { if ( $GetParam{TimeSearchType} eq 'TimeSlot' ) { my $StartDate = $LayoutObject->{LanguageObject}->FormatTimeString( $GetParam{ItemCreateTimeStartYear} . '-' . $GetParam{ItemCreateTimeStartMonth} . '-' . $GetParam{ItemCreateTimeStartDay} . ' 00:00:00', 'DateFormatShort' ); my $StopDate = $LayoutObject->{LanguageObject}->FormatTimeString( $GetParam{ItemCreateTimeStopYear} . '-' . $GetParam{ItemCreateTimeStopMonth} . '-' . $GetParam{ItemCreateTimeStopDay} . ' 00:00:00', 'DateFormatShort' ); $Attribute = Translatable('Created between'); $AttributeValue = $StartDate . ' ' . $LayoutObject->{LanguageObject}->Translate('and') . ' ' . $StopDate; } else { my $Mapping = { 'Last' => Translatable('Created within the last'), 'Before' => Translatable('Created more than ... ago'), }; $Attribute = $Mapping->{ $GetParam{ItemCreateTimePointStart} }; $AttributeValue = $GetParam{ItemCreateTimePoint} . ' ' . $LayoutObject->{LanguageObject}->Translate( $GetParam{ItemCreateTimePointFormat} . '(s)' ); } } elsif ( $Attribute eq 'VoteSearchType' ) { next ATTRIBUTE if !$GetParam{VoteSearchOption}; $AttributeValue = $LayoutObject->{LanguageObject}->Translate( $GetParam{VoteSearchType} ) . ' ' . $GetParam{VoteSearch}; } elsif ( $Attribute eq 'RateSearchType' ) { next ATTRIBUTE if !$GetParam{RateSearchOption}; $AttributeValue = $LayoutObject->{LanguageObject}->Translate( $GetParam{RateSearchType} ) . ' ' . $GetParam{RateSearch} . '%'; } $LayoutObject->Block( Name => 'SearchTerms', Data => { Attribute => $AttributeName, Value => $AttributeValue, }, ); } } # cycle through the activated Dynamic Fields for this screen DYNAMICFIELD: for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); if ( !$DynamicFieldSearchDisplay{ 'DynamicField_' . $DynamicFieldConfig->{Name} } ) { next DYNAMICFIELD; } $LayoutObject->Block( Name => 'SearchTerms', Data => { Attribute => $DynamicFieldConfig->{Label}, Value => $DynamicFieldSearchDisplay{ 'DynamicField_' . $DynamicFieldConfig->{Name} }, }, ); } # build search navigation bar my %PageNav = $LayoutObject->PageNavBar( Limit => $SearchLimit, StartHit => $StartHit, PageShown => $SearchPageShown, AllHits => $Counter, Action => "Action=PublicFAQSearch;Subaction=Search", Link => "$Self->{Profile}SortBy=$SortBy;Order=$OrderBy;", IDPrefix => "PublicFAQSearch", ); # show footer filter - show only if more the one page is available if ( defined $PageNav{TotalHits} && ( $PageNav{TotalHits} > $SearchPageShown ) ) { $LayoutObject->Block( Name => 'Pagination', Data => { %Param, %PageNav, }, ); } # start HTML page my $Output = $LayoutObject->CustomerHeader(); #Set the SortBy Class my $SortClass; # this sets the opposite to the OrderBy parameter if ( $OrderBy eq 'Down' ) { $SortClass = 'SortAscending'; } elsif ( $OrderBy eq 'Up' ) { $SortClass = 'SortDescending'; } # set the SortBy Class to the correct field my %CSSSort; my $CSSSortBy = $SortBy . 'Sort'; $CSSSort{$CSSSortBy} = $SortClass; my %NewOrder = ( Down => Translatable('Up'), Up => Translatable('Down'), ); # show language header if ($MultiLanguage) { $LayoutObject->Block( Name => 'HeaderLanguage', Data => { %Param, %CSSSort, Order => $NewOrder{$OrderBy}, }, ); } $Output .= $LayoutObject->Output( TemplateFile => 'PublicFAQSearchResultShort', Data => { %Param, %PageNav, %CSSSort, Order => $NewOrder{$OrderBy}, Profile => $Self->{Profile}, }, ); # build footer $Output .= $LayoutObject->CustomerFooter(); return $Output; } # empty search site else { # create HTML strings for all dynamic fields my %DynamicFieldHTML; DYNAMICFIELD: for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); # get search field preferences my $SearchFieldPreferences = $DynamicFieldBackendObject->SearchFieldPreferences( DynamicFieldConfig => $DynamicFieldConfig, ); next DYNAMICFIELD if !IsArrayRefWithData($SearchFieldPreferences); PREFERENCE: for my $Preference ( @{$SearchFieldPreferences} ) { # get field HTML $DynamicFieldHTML{ $DynamicFieldConfig->{Name} . $Preference->{Type} } = $DynamicFieldBackendObject->SearchFieldRender( DynamicFieldConfig => $DynamicFieldConfig, Profile => \%GetParam, DefaultValue => $Config->{Defaults}->{DynamicField}->{ $DynamicFieldConfig->{Name} }, LayoutObject => $LayoutObject, ConfirmationCheckboxes => 1, Type => $Preference->{Type}, ); } } # generate search mask my $Output = $LayoutObject->CustomerHeader(); $Output .= $Self->MaskForm( %GetParam, Profile => $Self->{Profile}, Area => 'Public', DynamicFieldHTML => \%DynamicFieldHTML ); $Output .= $LayoutObject->CustomerFooter(); return $Output; } } sub MaskForm { my ( $Self, %Param ) = @_; my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); my $TreeView = 0; if ( $ConfigObject->Get('Ticket::Frontend::ListType') eq 'tree' ) { $TreeView = 1; } # set output formats list my %ResultForm = ( Normal => Translatable('Normal'), Print => Translatable('Print'), CSV => Translatable('CSV') ); my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); # build output formats list $Param{ResultFormStrg} = $LayoutObject->BuildSelection( Data => {%ResultForm}, Name => 'ResultForm', SelectedID => $Param{ResultForm} || 'Normal', Class => 'Modernize', ); my $FAQObject = $Kernel::OM->Get('Kernel::System::FAQ'); my %Languages = $FAQObject->LanguageList( UserID => $Self->{UserID}, ); # build languages output list $Param{LanguagesStrg} = $LayoutObject->BuildSelection( Data => {%Languages}, Name => 'LanguageIDs', Size => 5, Multiple => 1, SelectedID => $Param{LanguageIDs}, Class => 'Modernize', ); my $Categories = $FAQObject->GetPublicCategoriesLongNames( CustomerUser => $Self->{UserLogin}, Type => 'rw', UserID => $Self->{UserID}, ); # build categories output list $Param{CategoriesStrg} = $LayoutObject->BuildSelection( Data => $Categories, Name => 'CategoryIDs', Size => 5, Multiple => 1, SelectedID => $Param{CategoryIDs}, TreeView => $TreeView, Class => 'Modernize', ); my %VotingOperators = ( Equals => Translatable('Equals'), GreaterThan => Translatable('Greater than'), GreaterThanEquals => Translatable('Greater than equals'), SmallerThan => Translatable('Smaller than'), SmallerThanEquals => Translatable('Smaller than equals'), ); $Param{VoteSearchTypeSelectionString} = $LayoutObject->BuildSelection( Data => \%VotingOperators, Name => 'VoteSearchType', Size => 1, SelectedID => $Param{VoteSearchType} || '', Translation => 1, Multiple => 0, Class => 'Modernize', ); $Param{RateSearchTypeSelectionString} = $LayoutObject->BuildSelection( Data => \%VotingOperators, Name => 'RateSearchType', Size => 1, SelectedID => $Param{RateSearchType} || '', Translation => 1, Multiple => 0, Class => 'Modernize', ); $Param{RateSearchSelectionString} = $LayoutObject->BuildSelection( Data => { 0 => '0%', 25 => '25%', 50 => '50%', 75 => '75%', 100 => '100%', }, Sort => 'NumericKey', Name => 'RateSearch', Size => 1, SelectedID => $Param{RateSearch} || '', Translation => 0, Multiple => 0, Class => 'Modernize', ); $Param{ItemCreateTimePoint} = $LayoutObject->BuildSelection( Data => [ 1 .. 59 ], Translation => 0, Name => 'ItemCreateTimePoint', SelectedID => $Param{ItemCreateTimePoint}, ); $Param{ItemCreateTimePointStart} = $LayoutObject->BuildSelection( Data => { Last => Translatable('within the last ...'), Before => Translatable('more than ... ago'), }, Translation => 1, Name => 'ItemCreateTimePointStart', SelectedID => $Param{ItemCreateTimePointStart} || 'Last', ); $Param{ItemCreateTimePointFormat} = $LayoutObject->BuildSelection( Data => { minute => Translatable('minute(s)'), hour => Translatable('hour(s)'), day => Translatable('day(s)'), week => Translatable('week(s)'), month => Translatable('month(s)'), year => Translatable('year(s)'), }, Translation => 1, Name => 'ItemCreateTimePointFormat', SelectedID => $Param{ItemCreateTimePointFormat}, ); $Param{ItemCreateTimeStart} = $LayoutObject->BuildDateSelection( %Param, Prefix => 'ItemCreateTimeStart', Format => 'DateInputFormat', DiffTime => -( ( 60 * 60 * 24 ) * 30 ), ); $Param{ItemCreateTimeStop} = $LayoutObject->BuildDateSelection( %Param, Prefix => 'ItemCreateTimeStop', Format => 'DateInputFormat', ); # HTML search mask output $LayoutObject->Block( Name => 'Search', Data => {%Param}, ); # output Dynamic fields blocks DYNAMICFIELDCONFIG: for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { next DYNAMICFIELDCONFIG if !IsHashRefWithData($DynamicFieldConfig); # get search field preferences my $SearchFieldPreferences = $Kernel::OM->Get('Kernel::System::DynamicField::Backend')->SearchFieldPreferences( DynamicFieldConfig => $DynamicFieldConfig, ); next DYNAMICFIELDCONFIG if !IsArrayRefWithData($SearchFieldPreferences); PREFERENCE: for my $Preference ( @{$SearchFieldPreferences} ) { my $DynamicFieldHTML = $Param{DynamicFieldHTML}->{ $DynamicFieldConfig->{Name} . $Preference->{Type} }; # skip fields that HTML could not be retrieved next PREFERENCE if !IsHashRefWithData($DynamicFieldHTML); $LayoutObject->Block( Name => 'DynamicField', Data => { Label => $DynamicFieldHTML->{Label}, Field => $DynamicFieldHTML->{Field}, }, ); } } # get multi-language default option my $MultiLanguage = $ConfigObject->Get('FAQ::MultiLanguage'); # show languages select if ($MultiLanguage) { $LayoutObject->Block( Name => 'Language', Data => {%Param}, ); } # HTML search mask output return $LayoutObject->Output( TemplateFile => 'PublicFAQSearch', Data => {%Param}, ); } 1;