1# -- 2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ 3# -- 4# This software comes with ABSOLUTELY NO WARRANTY. For details, see 5# the enclosed file COPYING for license information (GPL). If you 6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. 7# -- 8 9package Kernel::Output::HTML::Article::MIMEBase; 10 11use strict; 12use warnings; 13 14use parent 'Kernel::Output::HTML::Article::Base'; 15 16use Mail::Address; 17 18use Kernel::Language qw(Translatable); 19use Kernel::System::VariableCheck qw(:all); 20 21our @ObjectDependencies = ( 22 'Kernel::Config', 23 'Kernel::Output::HTML::Layout', 24 'Kernel::System::CheckItem', 25 'Kernel::System::CustomerUser', 26 'Kernel::System::Encode', 27 'Kernel::System::Log', 28 'Kernel::System::Main', 29 'Kernel::System::Ticket', 30 'Kernel::System::Ticket::Article', 31); 32 33sub new { 34 my ( $Type, %Param ) = @_; 35 36 my $Self = {}; 37 bless( $Self, $Type ); 38 39 return $Self; 40} 41 42=head2 ArticleFields() 43 44Returns common article fields for a MIMEBase article. 45 46 my %ArticleFields = $LayoutObject->ArticleFields( 47 TicketID => 123, # (required) 48 ArticleID => 123, # (required) 49 ); 50 51Returns: 52 53 %ArticleFields = ( 54 Sender => { # mandatory 55 Label => 'Sender', 56 Value => 'John Doe', 57 Prio => 100, 58 }, 59 Subject => { # mandatory 60 Label => 'Subject', 61 Value => 'Article subject', 62 Prio => 200, 63 }, 64 PGP => { 65 Label => 'PGP', # mandatory 66 Value => 'Value', # mandatory 67 Class => 'Class', # optional 68 ... 69 }, 70 ... 71 ); 72 73=cut 74 75sub ArticleFields { 76 my ( $Self, %Param ) = @_; 77 78 # Check needed stuff. 79 for my $Needed (qw(TicketID ArticleID)) { 80 if ( !$Param{$Needed} ) { 81 $Kernel::OM->Get('Kernel::System::Log')->Log( 82 Priority => 'error', 83 Message => "Need $Needed!", 84 ); 85 return; 86 } 87 } 88 89 my %Result; 90 91 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 92 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 93 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 94 my $ArticleBackendObject = $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForArticle(%Param); 95 96 my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet( 97 %Param, 98 DynamicFields => 0, 99 ); 100 101 my %Article = $ArticleBackendObject->ArticleGet( 102 %Param, 103 DynamicFields => 1, 104 RealNames => 1, 105 ); 106 107 # cleanup subject 108 $Article{Subject} = $Kernel::OM->Get('Kernel::System::Ticket')->TicketSubjectClean( 109 TicketNumber => $Ticket{TicketNumber}, 110 Subject => $Article{Subject} || '', 111 Size => 0, 112 ); 113 114 $Result{Subject} = { 115 Label => 'Subject', 116 Value => $Article{Subject}, 117 }; 118 119 # run article view modules 120 my $Config = $ConfigObject->Get('Ticket::Frontend::ArticleViewModule'); 121 122 if ( ref $Config eq 'HASH' ) { 123 my %Jobs = %{$Config}; 124 125 JOB: 126 for my $Job ( sort keys %Jobs ) { 127 128 # load module 129 next JOB if !$MainObject->Require( $Jobs{$Job}->{Module} ); 130 131 my $Object = $Jobs{$Job}->{Module}->new( 132 TicketID => $Self->{TicketID}, 133 ArticleID => $Param{ArticleID}, 134 UserID => $Param{UserID} || 1, 135 ); 136 137 # run module 138 my @Data = $Object->Check( 139 Article => \%Article, 140 %Ticket, Config => $Jobs{$Job} 141 ); 142 for my $DataRef (@Data) { 143 if ( !$DataRef->{Successful} ) { 144 $DataRef->{Result} = 'Error'; 145 } 146 else { 147 $DataRef->{Result} = 'Notice'; 148 } 149 150 $Result{ $DataRef->{Key} } = { 151 Label => $DataRef->{Key}, 152 Value => $DataRef->{Value}, 153 Class => $DataRef->{Result}, 154 Type => 'ArticleOption', 155 }; 156 157 for my $Warning ( @{ $DataRef->{Warnings} } ) { 158 $Result{ $DataRef->{Key} } = { 159 Label => $Warning->{Key}, 160 Value => $Warning->{Value}, 161 Class => $Warning->{Result}, 162 Type => 'ArticleOption', 163 }; 164 } 165 } 166 167 # TODO: Check how to implement this. 168 # # filter option 169 # $Object->Filter( 170 # Article => \%Article, 171 # %Ticket, Config => $Jobs{$Job} 172 # ); 173 } 174 } 175 176 # do some strips && quoting 177 my $RecipientDisplayType = $ConfigObject->Get('Ticket::Frontend::DefaultRecipientDisplayType') || 'Realname'; 178 my $SenderDisplayType = $ConfigObject->Get('Ticket::Frontend::DefaultSenderDisplayType') || 'Realname'; 179 KEY: 180 for my $Key (qw(From To Cc Bcc)) { 181 next KEY if !$Article{$Key}; 182 183 my $DisplayType = $Key eq 'From' ? $SenderDisplayType : $RecipientDisplayType; 184 my $HiddenType = $DisplayType eq 'Realname' ? 'Value' : 'Realname'; 185 $Result{$Key} = { 186 Label => $Key, 187 Value => $Article{$Key}, 188 Realname => $Article{ $Key . 'Realname' }, 189 ArticleID => $Article{ArticleID}, 190 $HiddenType . Hidden => 'Hidden', 191 HideInCustomerInterface => $Key eq 'Bcc' ? 1 : undef, 192 }; 193 if ( $Key eq 'From' ) { 194 $Result{Sender} = { 195 Label => Translatable('Sender'), 196 Value => $Article{From}, 197 Realname => $Article{FromRealname}, 198 $HiddenType . Hidden => 'Hidden', 199 HideInTimelineView => 1, 200 HideInTicketPrint => 1, 201 }; 202 } 203 } 204 205 # Assign priority. 206 my $Priority = 100; 207 for my $Key (qw(From To Cc Bcc)) { 208 if ( $Result{$Key} ) { 209 $Result{$Key}->{Prio} = $Priority; 210 $Priority += 100; 211 } 212 } 213 214 my @FieldsWithoutPrio = grep { !$Result{$_}->{Prio} } sort keys %Result; 215 216 my $BasePrio = 100000; 217 for my $Key (@FieldsWithoutPrio) { 218 $Result{$Key}->{Prio} = $BasePrio++; 219 } 220 221 return %Result; 222} 223 224=head2 ArticlePreview() 225 226Returns article preview for a MIMEBase article. 227 228 $LayoutObject->ArticlePreview( 229 TicketID => 123, # (required) 230 ArticleID => 123, # (required) 231 ResultType => 'plain', # (optional) plain|HTML. Default HTML. 232 MaxLength => 50, # (optional) performs trimming (for plain result only) 233 ); 234 235Returns article preview in scalar form: 236 237 $ArticlePreview = 'Hello, world!'; 238 239If HTML preview was requested, but HTML content does not exist for an article, this function will return undef. 240 241=cut 242 243sub ArticlePreview { 244 my ( $Self, %Param ) = @_; 245 246 # Check needed stuff. 247 for my $Needed (qw(TicketID ArticleID)) { 248 if ( !$Param{$Needed} ) { 249 $Kernel::OM->Get('Kernel::System::Log')->Log( 250 Priority => 'error', 251 Message => "Need $Needed!", 252 ); 253 return; 254 } 255 } 256 257 if ( $Param{MaxLength} && !IsPositiveInteger( $Param{MaxLength} ) ) { 258 $Kernel::OM->Get('Kernel::System::Log')->Log( 259 Priority => 'error', 260 Message => "MaxLength must be positive integer!" 261 ); 262 263 return; 264 } 265 266 my $ArticleBackendObject = $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForArticle(%Param); 267 268 my %Article = $ArticleBackendObject->ArticleGet( 269 %Param, 270 DynamicFields => 0, 271 ); 272 273 my $Result; 274 275 if ( $Param{ResultType} && $Param{ResultType} eq 'plain' ) { 276 277 # plain 278 $Result = $Article{Body}; 279 280 if ( $Param{MaxLength} ) { 281 282 # trim 283 $Result = substr( $Result, 0, $Param{MaxLength} ); 284 } 285 } 286 else { 287 my $HTMLBodyAttachmentID = $Self->HTMLBodyAttachmentIDGet(%Param); 288 289 if ($HTMLBodyAttachmentID) { 290 291 # Preview doesn't include inline images... 292 my %Data = $ArticleBackendObject->ArticleAttachment( 293 ArticleID => $Param{ArticleID}, 294 FileID => $HTMLBodyAttachmentID, 295 ); 296 297 # Get the charset directly from the attachment hash and convert content to the internal charset (utf-8). 298 # Please see bug#13367 for more information. 299 my $Charset; 300 if ( $Data{ContentType} =~ m/.+?charset=("|'|)(?<Charset>.+)/ig ) { 301 $Charset = $+{Charset}; 302 $Charset =~ s/"|'//g; 303 } 304 else { 305 $Charset = 'us-ascii'; 306 } 307 308 $Result = $Kernel::OM->Get('Kernel::System::Encode')->Convert( 309 Text => $Data{Content}, 310 From => $Charset, 311 To => 'utf-8', 312 Check => 1, 313 ); 314 } 315 } 316 317 return $Result; 318} 319 320=head2 HTMLBodyAttachmentIDGet() 321 322Returns HTMLBodyAttachmentID. 323 324 my $HTMLBodyAttachmentID = $LayoutObject->HTMLBodyAttachmentIDGet( 325 TicketID => 123, # (required) 326 ArticleID => 123, # (required) 327 ); 328 329Returns 330 331 $HTMLBodyAttachmentID = 23; 332 333=cut 334 335sub HTMLBodyAttachmentIDGet { 336 my ( $Self, %Param ) = @_; 337 338 # Check needed stuff. 339 for my $Needed (qw(TicketID ArticleID)) { 340 if ( !$Param{$Needed} ) { 341 $Kernel::OM->Get('Kernel::System::Log')->Log( 342 Priority => 'error', 343 Message => "Need $Needed!", 344 ); 345 return; 346 } 347 } 348 349 my $ArticleBackendObject = $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForArticle(%Param); 350 351 # Get a HTML attachment. 352 my %AttachmentIndexHTMLBody = $ArticleBackendObject->ArticleAttachmentIndex( 353 ArticleID => $Param{ArticleID}, 354 OnlyHTMLBody => 1, 355 ); 356 357 my ($HTMLBodyAttachmentID) = sort keys %AttachmentIndexHTMLBody; 358 359 return $HTMLBodyAttachmentID; 360} 361 362=head2 ArticleCustomerRecipientsGet() 363 364Get customer users from an article to use as recipients. 365 366 my @CustomerUserIDs = $LayoutObject->ArticleCustomerRecipientsGet( 367 TicketID => 123, # (required) 368 ArticleID => 123, # (required) 369 ); 370 371Returns array of customer user IDs who should receive a message: 372 373 @CustomerUserIDs = ( 374 'customer-1', 375 'customer-2', 376 ... 377 ); 378 379=cut 380 381sub ArticleCustomerRecipientsGet { 382 my ( $Self, %Param ) = @_; 383 384 for my $Needed (qw(TicketID ArticleID)) { 385 if ( !$Param{$Needed} ) { 386 $Kernel::OM->Get('Kernel::System::Log')->Log( 387 Priority => 'error', 388 Message => "Need $Needed!", 389 ); 390 return; 391 } 392 } 393 394 my %Article = $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForArticle(%Param)->ArticleGet(%Param); 395 return if !%Article; 396 397 my $RecipientEmail = $Article{From}; 398 399 # Check ReplyTo. 400 if ( $Article{ReplyTo} ) { 401 $RecipientEmail = $Article{ReplyTo}; 402 } 403 404 # Check article type and use To in case sender is not customer. 405 if ( $Article{SenderType} !~ /customer/ ) { 406 $RecipientEmail = $Article{To}; 407 $Article{ReplyTo} = ''; 408 } 409 410 my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); 411 my $CustomerUserObject = $Kernel::OM->Get('Kernel::System::CustomerUser'); 412 413 my @CustomerUserIDs; 414 415 EMAIL: 416 for my $Email ( Mail::Address->parse($RecipientEmail) ) { 417 next EMAIL if !$CheckItemObject->CheckEmail( Address => $Email->address() ); 418 419 # Get single customer user from customer backend based on the email address. 420 my %CustomerSearch = $CustomerUserObject->CustomerSearch( 421 PostMasterSearch => $Email->address(), 422 Limit => 1, 423 ); 424 next EMAIL if !%CustomerSearch; 425 426 # Save customer user ID if not already present in the list. 427 for my $CustomerUserID ( sort keys %CustomerSearch ) { 428 push @CustomerUserIDs, $CustomerUserID if !grep { $_ eq $CustomerUserID } @CustomerUserIDs; 429 } 430 } 431 432 return @CustomerUserIDs; 433} 434 4351; 436