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::System::DynamicField::Driver::Date; 10 11use strict; 12use warnings; 13 14use Kernel::System::VariableCheck qw(:all); 15 16use Kernel::Language qw(Translatable); 17 18use parent qw(Kernel::System::DynamicField::Driver::BaseDateTime); 19 20our @ObjectDependencies = ( 21 'Kernel::Config', 22 'Kernel::System::DateTime', 23 'Kernel::System::DB', 24 'Kernel::System::DynamicFieldValue', 25 'Kernel::System::Main', 26 'Kernel::System::Log', 27); 28 29=head1 NAME 30 31Kernel::System::DynamicField::Driver::Date 32 33=head1 DESCRIPTION 34 35DynamicFields Date Driver delegate 36 37=head1 PUBLIC INTERFACE 38 39This module implements the public interface of L<Kernel::System::DynamicField::Backend>. 40Please look there for a detailed reference of the functions. 41 42=head2 new() 43 44usually, you want to create an instance of this 45by using Kernel::System::DynamicField::Backend->new(); 46 47=cut 48 49sub new { 50 my ( $Type, %Param ) = @_; 51 52 # allocate new hash for object 53 my $Self = {}; 54 bless( $Self, $Type ); 55 56 # set field behaviors 57 $Self->{Behaviors} = { 58 'IsACLReducible' => 0, 59 'IsNotificationEventCondition' => 0, 60 'IsSortable' => 1, 61 'IsFiltrable' => 0, 62 'IsStatsCondition' => 1, 63 'IsCustomerInterfaceCapable' => 1, 64 }; 65 66 # get the Dynamic Field Backend custom extensions 67 my $DynamicFieldDriverExtensions 68 = $Kernel::OM->Get('Kernel::Config')->Get('DynamicFields::Extension::Driver::Date'); 69 70 EXTENSION: 71 for my $ExtensionKey ( sort keys %{$DynamicFieldDriverExtensions} ) { 72 73 # skip invalid extensions 74 next EXTENSION if !IsHashRefWithData( $DynamicFieldDriverExtensions->{$ExtensionKey} ); 75 76 # create a extension config shortcut 77 my $Extension = $DynamicFieldDriverExtensions->{$ExtensionKey}; 78 79 # check if extension has a new module 80 if ( $Extension->{Module} ) { 81 82 # check if module can be loaded 83 if ( 84 !$Kernel::OM->Get('Kernel::System::Main')->RequireBaseClass( $Extension->{Module} ) 85 ) 86 { 87 die "Can't load dynamic fields backend module" 88 . " $Extension->{Module}! $@"; 89 } 90 } 91 92 # check if extension contains more behaviors 93 if ( IsHashRefWithData( $Extension->{Behaviors} ) ) { 94 95 %{ $Self->{Behaviors} } = ( 96 %{ $Self->{Behaviors} }, 97 %{ $Extension->{Behaviors} } 98 ); 99 } 100 } 101 102 return $Self; 103} 104 105sub ValueSet { 106 my ( $Self, %Param ) = @_; 107 108 # Convert the ISO date string to a ISO date time string, if only the date is given to 109 # have the correct format. 110 $Param{Value} = $Self->_ConvertDate2DateTime( $Param{Value} ); 111 112 # check for no time in date fields 113 if ( $Param{Value} && $Param{Value} !~ m{\A \d{4}-\d{2}-\d{2}\s00:00:00 \z}xms ) { 114 $Kernel::OM->Get('Kernel::System::Log')->Log( 115 Priority => 'error', 116 Message => "The value for the field Date is invalid!\n" 117 . "The date must be valid and the time must be 00:00:00", 118 ); 119 return; 120 } 121 122 my $Success = $Kernel::OM->Get('Kernel::System::DynamicFieldValue')->ValueSet( 123 FieldID => $Param{DynamicFieldConfig}->{ID}, 124 ObjectID => $Param{ObjectID}, 125 Value => [ 126 { 127 ValueDateTime => $Param{Value}, 128 }, 129 ], 130 UserID => $Param{UserID}, 131 ); 132 133 return $Success; 134} 135 136sub ValueValidate { 137 my ( $Self, %Param ) = @_; 138 139 my $Prefix = 'DynamicField_' . $Param{DynamicFieldConfig}->{Name}; 140 my $DateRestriction = $Param{DynamicFieldConfig}->{Config}->{DateRestriction}; 141 142 # Convert the ISO date string to a ISO date time string, if only the date is given to 143 # have the correct format. 144 $Param{Value} = $Self->_ConvertDate2DateTime( $Param{Value} ); 145 146 # check for no time in date fields 147 if ( 148 $Param{Value} 149 && $Param{Value} !~ m{\A \d{4}-\d{2}-\d{2}\s00:00:00 \z}xms 150 && $Param{Value} !~ m{\A \d{4}-\d{2}-\d{2}\s23:59:59 \z}xms 151 ) 152 { 153 $Kernel::OM->Get('Kernel::System::Log')->Log( 154 Priority => 'error', 155 Message => "The value for the Date field ($Param{DynamicFieldConfig}->{Name}) is invalid!\n" 156 . "The date must be valid and the time must be 00:00:00" 157 . " (or 23:59:59 for search parameters)", 158 ); 159 return; 160 } 161 162 my $Success = $Kernel::OM->Get('Kernel::System::DynamicFieldValue')->ValueValidate( 163 Value => { 164 ValueDateTime => $Param{Value}, 165 }, 166 UserID => $Param{UserID}, 167 ); 168 169 if ($DateRestriction) { 170 171 my $ValueSystemTimeObject = $Kernel::OM->Create( 172 'Kernel::System::DateTime', 173 ObjectParams => { 174 String => $Param{Value}, 175 }, 176 ); 177 178 my $SystemTimePastObject = $Kernel::OM->Create('Kernel::System::DateTime'); 179 my $SystemTimeFutureObject = $Kernel::OM->Create('Kernel::System::DateTime'); 180 181 # if validating date only value, allow today for selection 182 if ( $Param{DynamicFieldConfig}->{FieldType} eq 'Date' ) { 183 184 # calculate today system time boundaries 185 $SystemTimePastObject->Set( 186 Hour => 0, 187 Minute => 0, 188 Second => 0, 189 ); 190 191 $SystemTimeFutureObject->Set( 192 Hour => 23, 193 Minute => 59, 194 Second => 59, 195 ); 196 } 197 198 if ( $DateRestriction eq 'DisableFutureDates' && $ValueSystemTimeObject > $SystemTimeFutureObject ) { 199 $Kernel::OM->Get('Kernel::System::Log')->Log( 200 Priority => 'error', 201 Message => 202 "The value for the Date field ($Param{DynamicFieldConfig}->{Name}) is in the future! The date needs to be in the past!", 203 ); 204 return; 205 } 206 elsif ( $DateRestriction eq 'DisablePastDates' && $ValueSystemTimeObject < $SystemTimePastObject ) { 207 $Kernel::OM->Get('Kernel::System::Log')->Log( 208 Priority => 'error', 209 Message => 210 "The value for the Date field ($Param{DynamicFieldConfig}->{Name}) is in the past! The date needs to be in the future!", 211 ); 212 return; 213 } 214 } 215 216 return $Success; 217} 218 219sub SearchSQLGet { 220 my ( $Self, %Param ) = @_; 221 222 my %Operators = ( 223 Equals => '=', 224 GreaterThan => '>', 225 GreaterThanEquals => '>=', 226 SmallerThan => '<', 227 SmallerThanEquals => '<=', 228 ); 229 230 if ( $Param{Operator} eq 'Empty' ) { 231 if ( $Param{SearchTerm} ) { 232 return " $Param{TableAlias}.value_date IS NULL "; 233 } 234 else { 235 return " $Param{TableAlias}.value_date IS NOT NULL "; 236 } 237 } 238 elsif ( !$Operators{ $Param{Operator} } ) { 239 $Kernel::OM->Get('Kernel::System::Log')->Log( 240 'Priority' => 'error', 241 'Message' => "Unsupported Operator $Param{Operator}", 242 ); 243 return; 244 } 245 246 # Convert the ISO date string to a ISO date time string, if only the date is given to 247 # have the correct format. 248 $Param{SearchTerm} = $Self->_ConvertDate2DateTime( $Param{SearchTerm} ); 249 250 my $SQL = " $Param{TableAlias}.value_date $Operators{ $Param{Operator} } '" 251 . $Kernel::OM->Get('Kernel::System::DB')->Quote( $Param{SearchTerm} ) . "' "; 252 253 return $SQL; 254} 255 256sub EditFieldRender { 257 my ( $Self, %Param ) = @_; 258 259 # take config from field config 260 my $FieldConfig = $Param{DynamicFieldConfig}->{Config}; 261 my $FieldName = 'DynamicField_' . $Param{DynamicFieldConfig}->{Name}; 262 my $FieldLabel = $Param{DynamicFieldConfig}->{Label}; 263 264 my $Value; 265 266 # set the field value or default 267 if ( $Param{UseDefaultValue} ) { 268 $Value = $FieldConfig->{DefaultValue} || ''; 269 } 270 271 if ( defined $Param{Value} ) { 272 $Value = $Param{Value}; 273 } 274 if ($Value) { 275 my ( $Year, $Month, $Day, $Hour, $Minute, $Second ) = $Value =~ 276 m{ \A ( \d{4} ) - ( \d{2} ) - ( \d{2} ) \s ( \d{2} ) : ( \d{2} ) : ( \d{2} ) \z }xms; 277 278 # If a value is sent this value must be active, then the Used part needs to be set to 1 279 # otherwise user can easily forget to mark the checkbox and this could lead into data 280 # lost (Bug#8258). 281 $FieldConfig->{ $FieldName . 'Used' } = 1; 282 $FieldConfig->{ $FieldName . 'Year' } = $Year; 283 $FieldConfig->{ $FieldName . 'Month' } = $Month; 284 $FieldConfig->{ $FieldName . 'Day' } = $Day; 285 $FieldConfig->{ $FieldName . 'Hour' } = $Hour; 286 $FieldConfig->{ $FieldName . 'Minute' } = $Minute; 287 } 288 289 # extract the dynamic field value from the web request 290 my $FieldValues = $Self->EditFieldValueGet( 291 ReturnValueStructure => 1, 292 %Param, 293 ); 294 295 # set values from ParamObject if present 296 if ( defined $FieldValues && IsHashRefWithData($FieldValues) ) { 297 for my $Type (qw(Used Year Month Day Hour Minute)) { 298 $FieldConfig->{ $FieldName . $Type } = $FieldValues->{ $FieldName . $Type }; 299 } 300 } 301 302 # check and set class if necessary 303 my $FieldClass = 'DynamicFieldText'; 304 if ( defined $Param{Class} && $Param{Class} ne '' ) { 305 $FieldClass .= ' ' . $Param{Class}; 306 } 307 308 # set field as mandatory 309 if ( $Param{Mandatory} ) { 310 $FieldClass .= ' Validate_Required'; 311 } 312 313 # set error css class 314 if ( $Param{ServerError} ) { 315 $FieldClass .= ' ServerError'; 316 } 317 318 # to set the predefined based on a time difference 319 my $DiffTime = $FieldConfig->{DefaultValue}; 320 if ( !defined $DiffTime || $DiffTime !~ m/^ \s* -? \d+ \s* $/smx ) { 321 $DiffTime = 0; 322 } 323 324 # to set the years range 325 my %YearsPeriodRange; 326 if ( defined $FieldConfig->{YearsPeriod} && $FieldConfig->{YearsPeriod} eq '1' ) { 327 %YearsPeriodRange = ( 328 YearPeriodPast => $FieldConfig->{YearsInPast} || 0, 329 YearPeriodFuture => $FieldConfig->{YearsInFuture} || 0, 330 ); 331 } 332 333 # date restrictions 334 if ( $FieldConfig->{DateRestriction} ) { 335 if ( $FieldConfig->{DateRestriction} eq 'DisablePastDates' ) { 336 $FieldConfig->{ValidateDateInFuture} = 1; 337 } 338 elsif ( $FieldConfig->{DateRestriction} eq 'DisableFutureDates' ) { 339 $FieldConfig->{ValidateDateNotInFuture} = 1; 340 } 341 } 342 343 my $HTMLString = $Param{LayoutObject}->BuildDateSelection( 344 %Param, 345 Prefix => $FieldName, 346 Format => 'DateInputFormat', 347 $FieldName . 'Class' => $FieldClass, 348 DiffTime => $DiffTime, 349 $FieldName . Required => $Param{Mandatory} || 0, 350 $FieldName . Optional => 1, 351 Validate => 1, 352 %{$FieldConfig}, 353 %YearsPeriodRange, 354 OverrideTimeZone => 1, 355 ); 356 357 if ( $Param{Mandatory} ) { 358 my $DivID = $FieldName . 'UsedError'; 359 360 my $FieldRequiredMessage = $Param{LayoutObject}->{LanguageObject}->Translate("This field is required."); 361 362 # for client side validation 363 $HTMLString .= <<"EOF"; 364 365<div id="$DivID" class="TooltipErrorMessage"> 366 <p> 367 $FieldRequiredMessage 368 </p> 369</div> 370EOF 371 } 372 373 if ( $Param{ServerError} ) { 374 375 my $ErrorMessage = $Param{ErrorMessage} || 'This field is required.'; 376 $ErrorMessage = $Param{LayoutObject}->{LanguageObject}->Translate($ErrorMessage); 377 my $DivID = $FieldName . 'UsedServerError'; 378 379 # for server side validation 380 $HTMLString .= <<"EOF"; 381 382<div id="$DivID" class="TooltipErrorMessage"> 383 <p> 384 $ErrorMessage 385 </p> 386</div> 387EOF 388 } 389 390 # call EditLabelRender on the common Driver 391 my $LabelString = $Self->EditLabelRender( 392 %Param, 393 Mandatory => $Param{Mandatory} || '0', 394 FieldName => $FieldName . 'Used', 395 ); 396 397 my $Data = { 398 Field => $HTMLString, 399 Label => $LabelString, 400 }; 401 402 return $Data; 403} 404 405sub EditFieldValueGet { 406 my ( $Self, %Param ) = @_; 407 408 # set the Prefix as the dynamic field name 409 my $Prefix = 'DynamicField_' . $Param{DynamicFieldConfig}->{Name}; 410 411 my %DynamicFieldValues; 412 413 # check if there is a Template and retrieve the dynamic field value from there 414 if ( IsHashRefWithData( $Param{Template} ) && defined $Param{Template}->{ $Prefix . 'Used' } ) { 415 for my $Type (qw(Used Year Month Day)) { 416 $DynamicFieldValues{ $Prefix . $Type } = $Param{Template}->{ $Prefix . $Type } || 0; 417 } 418 } 419 420 # otherwise get dynamic field value from the web request 421 elsif ( 422 defined $Param{ParamObject} 423 && ref $Param{ParamObject} eq 'Kernel::System::Web::Request' 424 ) 425 { 426 for my $Type (qw(Used Year Month Day)) { 427 $DynamicFieldValues{ $Prefix . $Type } = $Param{ParamObject}->GetParam( 428 Param => $Prefix . $Type, 429 ) || 0; 430 } 431 } 432 433 # complete the rest of the date with 0s to have a valid Date/Time value 434 for my $Type (qw(Hour Minute)) { 435 $DynamicFieldValues{ $Prefix . $Type } = 0; 436 } 437 438 # return if the field is empty (e.g. initial screen) 439 return if !$DynamicFieldValues{ $Prefix . 'Used' } 440 && !$DynamicFieldValues{ $Prefix . 'Year' } 441 && !$DynamicFieldValues{ $Prefix . 'Month' } 442 && !$DynamicFieldValues{ $Prefix . 'Day' }; 443 444 # check if return value structure is needed 445 if ( defined $Param{ReturnValueStructure} && $Param{ReturnValueStructure} eq '1' ) { 446 return \%DynamicFieldValues; 447 } 448 449 # check if return template structure is needed 450 if ( defined $Param{ReturnTemplateStructure} && $Param{ReturnTemplateStructure} eq '1' ) { 451 return \%DynamicFieldValues; 452 } 453 454 # add seconds, as 0 to the DynamicFieldValues hash 455 $DynamicFieldValues{ 'DynamicField_' . $Param{DynamicFieldConfig}->{Name} . 'Second' } = 0; 456 457 my $ManualTimeStamp = ''; 458 459 if ( $DynamicFieldValues{ $Prefix . 'Used' } ) { 460 461 # add a leading zero for date parts that could be less than ten to generate a correct 462 # time stamp 463 for my $Type (qw(Month Day Hour Minute Second)) { 464 $DynamicFieldValues{ $Prefix . $Type } = sprintf "%02d", 465 $DynamicFieldValues{ $Prefix . $Type }; 466 } 467 my $Year = $DynamicFieldValues{ $Prefix . 'Year' } || '0000'; 468 my $Month = $DynamicFieldValues{ $Prefix . 'Month' } || '00'; 469 my $Day = $DynamicFieldValues{ $Prefix . 'Day' } || '00'; 470 my $Hour = '00'; 471 my $Minute = '00'; 472 my $Second = '00'; 473 474 $ManualTimeStamp = 475 $Year . '-' . $Month . '-' . $Day . ' ' 476 . $Hour . ':' . $Minute . ':' . $Second; 477 } 478 479 return $ManualTimeStamp; 480} 481 482sub EditFieldValueValidate { 483 my ( $Self, %Param ) = @_; 484 485 # get the field value from the http request 486 my $Value = $Self->EditFieldValueGet( 487 DynamicFieldConfig => $Param{DynamicFieldConfig}, 488 ParamObject => $Param{ParamObject}, 489 ReturnValueStructure => 1, 490 ); 491 492 # on normal basis Used field could be empty but if there was no value from EditFieldValueGet() 493 # it must be an error 494 if ( !defined $Value ) { 495 return { 496 ServerError => 1, 497 ErrorMessage => 'Invalid Date!' 498 }; 499 } 500 501 my $ServerError; 502 my $ErrorMessage; 503 504 # set the date time prefix as field name 505 my $Prefix = 'DynamicField_' . $Param{DynamicFieldConfig}->{Name}; 506 507 # date restriction 508 my $DateRestriction = $Param{DynamicFieldConfig}->{Config}->{DateRestriction}; 509 510 # perform necessary validations 511 if ( $Param{Mandatory} && !$Value->{ $Prefix . 'Used' } ) { 512 $ServerError = 1; 513 } 514 515 if ( $Value->{ $Prefix . 'Used' } && $DateRestriction ) { 516 517 my $Year = $Value->{ $Prefix . 'Year' } || '0000'; 518 my $Month = $Value->{ $Prefix . 'Month' } || '00'; 519 my $Day = $Value->{ $Prefix . 'Day' } || '00'; 520 my $Hour = $Value->{ $Prefix . 'Hour' } || '00'; 521 my $Minute = $Value->{ $Prefix . 'Minute' } || '00'; 522 my $Second = $Value->{ $Prefix . 'Second' } || '00'; 523 524 my $ManualTimeStamp = 525 $Year . '-' . $Month . '-' . $Day . ' ' 526 . $Hour . ':' . $Minute . ':' . $Second; 527 528 # get time object 529 my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 530 531 my $ValueSystemTime = $DateTimeObject->Set( String => $ManualTimeStamp ); 532 $ValueSystemTime = $ValueSystemTime ? $DateTimeObject->ToEpoch() : undef; 533 534 my $SystemTime = $DateTimeObject->ToEpoch(); 535 my $SystemTimePast = $SystemTime; 536 my $SystemTimeFuture; 537 538 # if validating date only value, allow today for selection 539 if ( $Param{DynamicFieldConfig}->{FieldType} eq 'Date' ) { 540 541 # calculate today system time boundaries 542 $DateTimeObject->Set( 543 Hour => 0, 544 Minute => 0, 545 Second => 0 546 ); 547 $SystemTimePast = $DateTimeObject->ToEpoch(); 548 549 $DateTimeObject->Set( 550 Hour => 23, 551 Minute => 59, 552 Second => 59 553 ); 554 $SystemTimeFuture = $DateTimeObject->ToEpoch(); 555 } 556 557 if ( $DateRestriction eq 'DisableFutureDates' && $ValueSystemTime > $SystemTimeFuture ) { 558 $ServerError = 1; 559 $ErrorMessage = "Invalid date (need a past date)!"; 560 } 561 elsif ( $DateRestriction eq 'DisablePastDates' && $ValueSystemTime < $SystemTimePast ) { 562 $ServerError = 1; 563 $ErrorMessage = "Invalid date (need a future date)!"; 564 } 565 } 566 567 # create resulting structure 568 my $Result = { 569 ServerError => $ServerError, 570 ErrorMessage => $ErrorMessage, 571 }; 572 573 return $Result; 574} 575 576sub DisplayValueRender { 577 my ( $Self, %Param ) = @_; 578 579 my $Value = ''; 580 581 # convert date to localized string 582 if ( defined $Param{Value} ) { 583 $Value = $Param{LayoutObject}->{LanguageObject}->FormatTimeString( 584 $Param{Value}, 585 'DateFormatShort', 586 ); 587 588 } 589 590 # in this Driver there is no need for HTMLOutput 591 # Title is always equal to Value 592 my $Title = $Value; 593 594 # set field link form config 595 my $Link = $Param{DynamicFieldConfig}->{Config}->{Link} || ''; 596 my $LinkPreview = $Param{DynamicFieldConfig}->{Config}->{LinkPreview} || ''; 597 598 my $Data = { 599 Value => $Value, 600 Title => $Title, 601 Link => $Link, 602 LinkPreview => $LinkPreview, 603 }; 604 605 return $Data; 606} 607 608sub ReadableValueRender { 609 my ( $Self, %Param ) = @_; 610 611 my $Value = defined $Param{Value} ? $Param{Value} : ''; 612 613 # only keep date part, loose time part of time-stamp 614 $Value =~ s{ \A (\d{4} - \d{2} - \d{2}) .+?\z }{$1}xms; 615 616 # Title is always equal to Value 617 my $Title = $Value; 618 619 my $Data = { 620 Value => $Value, 621 Title => $Title, 622 }; 623 624 return $Data; 625} 626 627sub SearchFieldRender { 628 my ( $Self, %Param ) = @_; 629 630 # take config from field config 631 my $FieldConfig = $Param{DynamicFieldConfig}->{Config}; 632 my $FieldName = 'Search_DynamicField_' . $Param{DynamicFieldConfig}->{Name}; 633 634 # set the default type 635 $Param{Type} ||= 'TimeSlot'; 636 637 # add type to FieldName 638 $FieldName .= $Param{Type}; 639 640 my $FieldLabel = $Param{DynamicFieldConfig}->{Label}; 641 642 my $Value; 643 644 my %DefaultValue; 645 646 if ( defined $Param{DefaultValue} ) { 647 my @Items = split /;/, $Param{DefaultValue}; 648 649# format example of the key name for TimePoint: 650# 651# Search_DynamicField_DateTest1TimePointFormat=week;Search_DynamicField_DateTest1TimePointStart=Before;Search_DynamicField_DateTest1TimePointValue=7; 652 653# format example of the key name for TimeSlot: 654# 655# Search_DynamicField_DateTest1TimeSlotStartYear=1974;Search_DynamicField_DateTest1TimeSlotStartMonth=01;Search_DynamicField_DateTest1TimeSlotStartDay=26; 656# Search_DynamicField_DateTest1TimeSlotStartHour=00;Search_DynamicField_DateTest1TimeSlotStartMinute=00;Search_DynamicField_DateTest1TimeSlotStartSecond=00; 657# Search_DynamicField_DateTest1TimeSlotStopYear=2013;Search_DynamicField_DateTest1TimeSlotStopMonth=01;Search_DynamicField_DateTest1TimeSlotStopDay=26; 658# Search_DynamicField_DateTest1TimeSlotStopHour=23;Search_DynamicField_DateTest1TimeSlotStopMinute=59;Search_DynamicField_DateTest1TimeSlotStopSecond=59; 659 660 my $KeyName = 'Search_DynamicField_' . $Param{DynamicFieldConfig}->{Name} . $Param{Type}; 661 662 ITEM: 663 for my $Item (@Items) { 664 my ( $Key, $Value ) = split /=/, $Item; 665 666 # only handle keys that match the current type 667 next ITEM if $Key !~ m{ $Param{Type} }xms; 668 669 if ( $Param{Type} eq 'TimePoint' ) { 670 671 if ( $Key eq $KeyName . 'Format' ) { 672 $DefaultValue{Format}->{$Key} = $Value; 673 } 674 elsif ( $Key eq $KeyName . 'Start' ) { 675 $DefaultValue{Start}->{$Key} = $Value; 676 } 677 elsif ( $Key eq $KeyName . 'Value' ) { 678 $DefaultValue{Value}->{$Key} = $Value; 679 } 680 681 next ITEM; 682 } 683 if ( $Key =~ m{Start} ) { 684 $DefaultValue{ValueStart}->{$Key} = $Value; 685 } 686 elsif ( $Key =~ m{Stop} ) { 687 $DefaultValue{ValueStop}->{$Key} = $Value; 688 } 689 } 690 } 691 692 # set the field value 693 if (%DefaultValue) { 694 $Value = \%DefaultValue; 695 } 696 697 # get the field value, this function is always called after the profile is loaded 698 my $FieldValues = $Self->SearchFieldValueGet( 699 %Param, 700 ); 701 702 if ( 703 defined $FieldValues 704 && $Param{Type} eq 'TimeSlot' 705 && defined $FieldValues->{ValueStart} 706 && defined $FieldValues->{ValueStop} 707 ) 708 { 709 $Value = $FieldValues; 710 } 711 elsif ( 712 defined $FieldValues 713 && $Param{Type} eq 'TimePoint' 714 && defined $FieldValues->{Format} 715 && defined $FieldValues->{Start} 716 && defined $FieldValues->{Value} 717 ) 718 { 719 $Value = $FieldValues; 720 } 721 722 # check and set class if necessary 723 my $FieldClass = 'DynamicFieldDateTime'; 724 725 # set as checked if necessary 726 my $FieldChecked = ( defined $Value->{$FieldName} && $Value->{$FieldName} == 1 ? 'checked="checked"' : '' ); 727 728 my $HTMLString = <<"EOF"; 729 <input type="hidden" id="$FieldName" name="$FieldName" value="1"/> 730EOF 731 732 if ( $Param{ConfirmationCheckboxes} ) { 733 $HTMLString = <<"EOF"; 734 <input type="checkbox" id="$FieldName" name="$FieldName" value="1" $FieldChecked/> 735EOF 736 } 737 738 # build HTML for TimePoint 739 if ( $Param{Type} eq 'TimePoint' ) { 740 741 $HTMLString .= $Param{LayoutObject}->BuildSelection( 742 Data => { 743 'Before' => Translatable('more than ... ago'), 744 'Last' => Translatable('within the last ...'), 745 'Next' => Translatable('within the next ...'), 746 'After' => Translatable('in more than ...'), 747 }, 748 Sort => 'IndividualKey', 749 SortIndividual => [ 'Before', 'Last', 'Next', 'After' ], 750 Name => $FieldName . 'Start', 751 SelectedID => $Value->{Start}->{ $FieldName . 'Start' } || 'Last', 752 ); 753 $HTMLString .= ' ' . $Param{LayoutObject}->BuildSelection( 754 Data => [ 1 .. 59 ], 755 Name => $FieldName . 'Value', 756 SelectedID => $Value->{Value}->{ $FieldName . 'Value' } || 1, 757 ); 758 $HTMLString .= ' ' . $Param{LayoutObject}->BuildSelection( 759 Data => { 760 minute => Translatable('minute(s)'), 761 hour => Translatable('hour(s)'), 762 day => Translatable('day(s)'), 763 week => Translatable('week(s)'), 764 month => Translatable('month(s)'), 765 year => Translatable('year(s)'), 766 }, 767 Name => $FieldName . 'Format', 768 SelectedID => $Value->{Format}->{ $FieldName . 'Format' } || Translatable('day'), 769 ); 770 771 my $AdditionalText; 772 if ( $Param{UseLabelHints} ) { 773 $AdditionalText = Translatable('before/after'); 774 } 775 776 # call EditLabelRender on the common driver 777 my $LabelString = $Self->EditLabelRender( 778 %Param, 779 FieldName => $FieldName, 780 AdditionalText => $AdditionalText, 781 ); 782 783 my $Data = { 784 Field => $HTMLString, 785 Label => $LabelString, 786 }; 787 788 return $Data; 789 } 790 791 # to set the years range 792 my %YearsPeriodRange; 793 if ( defined $FieldConfig->{YearsPeriod} && $FieldConfig->{YearsPeriod} eq '1' ) { 794 %YearsPeriodRange = ( 795 YearPeriodPast => $FieldConfig->{YearsInPast} || 0, 796 YearPeriodFuture => $FieldConfig->{YearsInFuture} || 0, 797 ); 798 } 799 800 # build HTML for start value set 801 $HTMLString .= $Param{LayoutObject}->BuildDateSelection( 802 %Param, 803 Prefix => $FieldName . 'Start', 804 Format => 'DateInputFormat', 805 $FieldName . 'Class' => $FieldClass, 806 DiffTime => -( ( 60 * 60 * 24 ) * 30 ), 807 Validate => 1, 808 %{ $Value->{ValueStart} }, 809 %YearsPeriodRange, 810 OverrideTimeZone => 1, 811 ); 812 813 # build HTML for "and" separator 814 $HTMLString .= ' ' . $Param{LayoutObject}->{LanguageObject}->Translate("and") . "\n"; 815 816 # build HTML for stop value set 817 $HTMLString .= $Param{LayoutObject}->BuildDateSelection( 818 %Param, 819 Prefix => $FieldName . 'Stop', 820 Format => 'DateInputFormat', 821 $FieldName . 'Class' => $FieldClass, 822 DiffTime => +( ( 60 * 60 * 24 ) * 30 ), 823 Validate => 1, 824 %{ $Value->{ValueStop} }, 825 %YearsPeriodRange, 826 OverrideTimeZone => 1, 827 ); 828 829 my $AdditionalText; 830 if ( $Param{UseLabelHints} ) { 831 $AdditionalText = Translatable('between'); 832 } 833 834 # call EditLabelRender on the common Driver 835 my $LabelString = $Self->EditLabelRender( 836 %Param, 837 FieldName => $FieldName, 838 AdditionalText => $AdditionalText, 839 ); 840 841 my $Data = { 842 Field => $HTMLString, 843 Label => $LabelString, 844 }; 845 846 return $Data; 847} 848 849sub SearchFieldValueGet { 850 my ( $Self, %Param ) = @_; 851 852 # set the Prefix as the dynamic field name 853 my $Prefix = 'Search_DynamicField_' . $Param{DynamicFieldConfig}->{Name}; 854 855 # set the default type 856 $Param{Type} ||= 'TimeSlot'; 857 858 # add type to prefix 859 $Prefix .= $Param{Type}; 860 861 if ( $Param{Type} eq 'TimePoint' ) { 862 863 # get dynamic field value 864 my %DynamicFieldValues; 865 for my $Type (qw(Start Value Format)) { 866 867 # get dynamic field value form param object 868 if ( defined $Param{ParamObject} ) { 869 870 # return if value was not checked (useful in customer interface) 871 return if !$Param{ParamObject}->GetParam( Param => $Prefix ); 872 873 $DynamicFieldValues{ $Prefix . $Type } = $Param{ParamObject}->GetParam( 874 Param => $Prefix . $Type, 875 ); 876 } 877 878 # otherwise get the value from the profile 879 elsif ( defined $Param{Profile} ) { 880 881 # return if value was not checked (useful in customer interface) 882 return if !$Param{Profile}->{$Prefix}; 883 884 $DynamicFieldValues{ $Prefix . $Type } = $Param{Profile}->{ $Prefix . $Type }; 885 } 886 else { 887 return; 888 } 889 } 890 891 # return if the field is empty (e.g. initial screen) 892 return if !$DynamicFieldValues{ $Prefix . 'Start' } 893 && !$DynamicFieldValues{ $Prefix . 'Value' } 894 && !$DynamicFieldValues{ $Prefix . 'Format' }; 895 896 $DynamicFieldValues{$Prefix} = 1; 897 898 # check if return value structure is needed 899 if ( defined $Param{ReturnProfileStructure} && $Param{ReturnProfileStructure} eq '1' ) { 900 return \%DynamicFieldValues; 901 } 902 903 return { 904 Format => { 905 $Prefix . 'Format' => $DynamicFieldValues{ $Prefix . 'Format' } || 'Last', 906 }, 907 Start => { 908 $Prefix . 'Start' => $DynamicFieldValues{ $Prefix . 'Start' } || 'day', 909 }, 910 Value => { 911 $Prefix . 'Value' => $DynamicFieldValues{ $Prefix . 'Value' } || 1, 912 }, 913 $Prefix => 1, 914 }; 915 } 916 917 # get dynamic field value 918 my %DynamicFieldValues; 919 for my $Type (qw(Start Stop)) { 920 for my $Part (qw(Year Month Day)) { 921 922 # get dynamic field value from param object 923 if ( defined $Param{ParamObject} ) { 924 925 # return if value was not checked (useful in customer interface) 926 return if !$Param{ParamObject}->GetParam( Param => $Prefix ); 927 928 $DynamicFieldValues{ $Prefix . $Type . $Part } = $Param{ParamObject}->GetParam( 929 Param => $Prefix . $Type . $Part, 930 ); 931 } 932 933 # otherwise get the value from the profile 934 elsif ( defined $Param{Profile} ) { 935 936 # return if value was not checked (useful in customer interface) 937 return if !$Param{Profile}->{$Prefix}; 938 939 $DynamicFieldValues{ $Prefix . $Type . $Part } = $Param{Profile}->{ $Prefix . $Type . $Part }; 940 } 941 else { 942 return; 943 } 944 } 945 } 946 947 # return if the field is empty (e.g. initial screen) 948 return if !$DynamicFieldValues{ $Prefix . 'StartYear' } 949 && !$DynamicFieldValues{ $Prefix . 'StartMonth' } 950 && !$DynamicFieldValues{ $Prefix . 'StartDay' } 951 && !$DynamicFieldValues{ $Prefix . 'StopYear' } 952 && !$DynamicFieldValues{ $Prefix . 'StopMonth' } 953 && !$DynamicFieldValues{ $Prefix . 'StopDay' }; 954 955 $DynamicFieldValues{ $Prefix . 'StartHour' } = '00'; 956 $DynamicFieldValues{ $Prefix . 'StartMinute' } = '00'; 957 $DynamicFieldValues{ $Prefix . 'StartSecond' } = '00'; 958 $DynamicFieldValues{ $Prefix . 'StopHour' } = '23'; 959 $DynamicFieldValues{ $Prefix . 'StopMinute' } = '59'; 960 $DynamicFieldValues{ $Prefix . 'StopSecond' } = '59'; 961 962 $DynamicFieldValues{$Prefix} = 1; 963 964 # check if return value structure is needed 965 if ( defined $Param{ReturnProfileStructure} && $Param{ReturnProfileStructure} eq '1' ) { 966 return \%DynamicFieldValues; 967 } 968 969 # add a leading zero for date parts that could be less than ten to generate a correct 970 # time stamp 971 for my $Type (qw(Start Stop)) { 972 for my $Part (qw(Month Day Hour Minute Second)) { 973 $DynamicFieldValues{ $Prefix . $Type . $Part } = sprintf "%02d", 974 $DynamicFieldValues{ $Prefix . $Type . $Part }; 975 } 976 } 977 978 my $ValueStart = { 979 $Prefix . 'StartYear' => $DynamicFieldValues{ $Prefix . 'StartYear' } || '0000', 980 $Prefix . 'StartMonth' => $DynamicFieldValues{ $Prefix . 'StartMonth' } || '00', 981 $Prefix . 'StartDay' => $DynamicFieldValues{ $Prefix . 'StartDay' } || '00', 982 $Prefix . 'StartHour' => $DynamicFieldValues{ $Prefix . 'StartHour' } || '00', 983 $Prefix . 'StartMinute' => $DynamicFieldValues{ $Prefix . 'StartMinute' } || '00', 984 $Prefix . 'StartSecond' => $DynamicFieldValues{ $Prefix . 'StartSecond' } || '00', 985 }; 986 987 my $ValueStop = { 988 $Prefix . 'StopYear' => $DynamicFieldValues{ $Prefix . 'StopYear' } || '0000', 989 $Prefix . 'StopMonth' => $DynamicFieldValues{ $Prefix . 'StopMonth' } || '00', 990 $Prefix . 'StopDay' => $DynamicFieldValues{ $Prefix . 'StopDay' } || '00', 991 $Prefix . 'StopHour' => $DynamicFieldValues{ $Prefix . 'StopHour' } || '00', 992 $Prefix . 'StopMinute' => $DynamicFieldValues{ $Prefix . 'StopMinute' } || '00', 993 $Prefix . 'StopSecond' => $DynamicFieldValues{ $Prefix . 'StopSecond' } || '00', 994 }; 995 996 return { 997 $Prefix => 1, 998 ValueStart => $ValueStart, 999 ValueStop => $ValueStop, 1000 }; 1001} 1002 1003sub SearchFieldParameterBuild { 1004 my ( $Self, %Param ) = @_; 1005 1006 # set the default type 1007 $Param{Type} ||= 'TimeSlot'; 1008 1009 # get field value 1010 my $Value = $Self->SearchFieldValueGet(%Param); 1011 1012 my $DisplayValue; 1013 1014 if ( defined $Value && !$Value ) { 1015 $DisplayValue = ''; 1016 } 1017 1018 # do not search if value was not checked (useful for customer interface) 1019 if ( !$Value ) { 1020 return { 1021 Parameter => { 1022 Equals => $Value, 1023 }, 1024 Display => $DisplayValue, 1025 }; 1026 } 1027 1028 # search for a wild card in the value 1029 if ( $Value && IsHashRefWithData($Value) ) { 1030 1031 my $Prefix = 'Search_DynamicField_' . $Param{DynamicFieldConfig}->{Name}; 1032 $Prefix .= $Param{Type}; 1033 1034 if ( 1035 $Param{Type} eq 'TimePoint' 1036 && $Value->{Start}->{ $Prefix . 'Start' } 1037 && $Value->{Format}->{ $Prefix . 'Format' } 1038 && $Value->{Value}->{ $Prefix . 'Value' } 1039 && $Value->{$Prefix} 1040 ) 1041 { 1042 1043 # to store the search parameters 1044 my %Parameter; 1045 1046 # store in local variables for easier handling 1047 my $Format = $Value->{Format}->{ $Prefix . 'Format' }; 1048 my $Start = $Value->{Start}->{ $Prefix . 'Start' }; 1049 my $Value = $Value->{Value}->{ $Prefix . 'Value' }; 1050 1051 my $DiffTimeMinutes = 0; 1052 if ( $Format eq 'minute' ) { 1053 $DiffTimeMinutes = $Value; 1054 } 1055 elsif ( $Format eq 'hour' ) { 1056 $DiffTimeMinutes = $Value * 60; 1057 } 1058 elsif ( $Format eq 'day' ) { 1059 $DiffTimeMinutes = $Value * 60 * 24; 1060 } 1061 elsif ( $Format eq 'week' ) { 1062 $DiffTimeMinutes = $Value * 60 * 24 * 7; 1063 } 1064 elsif ( $Format eq 'month' ) { 1065 $DiffTimeMinutes = $Value * 60 * 24 * 30; 1066 } 1067 elsif ( $Format eq 'year' ) { 1068 $DiffTimeMinutes = $Value * 60 * 24 * 365; 1069 } 1070 1071 # get time object 1072 my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 1073 1074 # get the current time in epoch seconds 1075 my $Now = $DateTimeObject->ToEpoch(); 1076 1077 # calculate difference time seconds 1078 my $DiffTimeSeconds = $DiffTimeMinutes * 60; 1079 1080 my $DisplayValue = ''; 1081 1082 # define to search before or after that time stamp 1083 if ( $Start eq 'Before' ) { 1084 1085 # we must subtract the difference because it is in the past 1086 my $DateTimeObjectBefore = $Kernel::OM->Create( 1087 'Kernel::System::DateTime', 1088 ObjectParams => { 1089 Epoch => $Now - $DiffTimeSeconds, 1090 } 1091 ); 1092 1093 # only search dates in the past (before the time stamp) 1094 my $YearMonthDay = $DateTimeObjectBefore->Format( Format => '%Y-%m-%d' ); 1095 1096 $Parameter{SmallerThan} = $YearMonthDay . ' 00:00:00'; 1097 1098 # set the display value 1099 $DisplayValue = '< ' . $YearMonthDay; 1100 1101 } 1102 elsif ( $Start eq 'Last' ) { 1103 1104 my $DateTimeObjectLast = $Kernel::OM->Create( 1105 'Kernel::System::DateTime', 1106 ObjectParams => { 1107 Epoch => $Now - $DiffTimeSeconds, 1108 } 1109 ); 1110 1111 my $YearMonthDay = $DateTimeObjectLast->Format( Format => '%Y-%m-%d' ); 1112 $Parameter{GreaterThanEquals} = $YearMonthDay . ' 00:00:00'; 1113 1114 # set the display value 1115 $DisplayValue = $YearMonthDay; 1116 1117 # using DateTimeObject created outside these if 1118 $YearMonthDay = $DateTimeObject->Format( Format => '%Y-%m-%d' ); 1119 1120 $Parameter{SmallerThanEquals} = $YearMonthDay . ' 23:59:59'; 1121 1122 $DisplayValue .= ' - ' . $YearMonthDay; 1123 1124 } 1125 elsif ( $Start eq 'Next' ) { 1126 1127 my $DateTimeObjectNext = $DateTimeObject->Clone(); 1128 1129 my $YearMonthDay = $DateTimeObjectNext->Format( Format => '%Y-%m-%d' ); 1130 1131 # set the display value 1132 $DisplayValue = $YearMonthDay; 1133 1134 $Parameter{GreaterThanEquals} = $YearMonthDay . ' 00:00:00'; 1135 1136 $DateTimeObjectNext = $Kernel::OM->Create( 1137 'Kernel::System::DateTime', 1138 ObjectParams => { 1139 Epoch => $Now + $DiffTimeSeconds, 1140 } 1141 ); 1142 1143 $YearMonthDay = $DateTimeObjectNext->Format( Format => '%Y-%m-%d' ); 1144 1145 $DisplayValue .= ' - ' . $YearMonthDay; 1146 1147 $Parameter{SmallerThanEquals} = $YearMonthDay . ' 23:59:59'; 1148 1149 } 1150 elsif ( $Start eq 'After' ) { 1151 1152 my $DateTimeObjectAfter = $Kernel::OM->Create( 1153 'Kernel::System::DateTime', 1154 ObjectParams => { 1155 Epoch => $Now + $DiffTimeSeconds, 1156 } 1157 ); 1158 1159 my $YearMonthDay = $DateTimeObjectAfter->Format( Format => '%Y-%m-%d' ); 1160 1161 $Parameter{GreaterThan} = $YearMonthDay . ' 23:59:59'; 1162 1163 $DisplayValue = '> ' . $YearMonthDay; 1164 1165 } 1166 1167 # return search parameter structure 1168 return { 1169 Parameter => \%Parameter, 1170 Display => $DisplayValue, 1171 }; 1172 } 1173 1174 my $ValueStart = $Value->{ValueStart}->{ $Prefix . 'StartYear' } . '-' 1175 . $Value->{ValueStart}->{ $Prefix . 'StartMonth' } . '-' 1176 . $Value->{ValueStart}->{ $Prefix . 'StartDay' } . ' ' 1177 . $Value->{ValueStart}->{ $Prefix . 'StartHour' } . ':' 1178 . $Value->{ValueStart}->{ $Prefix . 'StartMinute' } . ':' 1179 . $Value->{ValueStart}->{ $Prefix . 'StartSecond' }; 1180 1181 my $ValueStop = $Value->{ValueStop}->{ $Prefix . 'StopYear' } . '-' 1182 . $Value->{ValueStop}->{ $Prefix . 'StopMonth' } . '-' 1183 . $Value->{ValueStop}->{ $Prefix . 'StopDay' } . ' ' 1184 . $Value->{ValueStop}->{ $Prefix . 'StopHour' } . ':' 1185 . $Value->{ValueStop}->{ $Prefix . 'StopMinute' } . ':' 1186 . $Value->{ValueStop}->{ $Prefix . 'StopSecond' }; 1187 1188 my $DisplayValueStart = $Value->{ValueStart}->{ $Prefix . 'StartYear' } . '-' 1189 . $Value->{ValueStart}->{ $Prefix . 'StartMonth' } . '-' 1190 . $Value->{ValueStart}->{ $Prefix . 'StartDay' }; 1191 1192 my $DisplayValueStop = $Value->{ValueStop}->{ $Prefix . 'StopYear' } . '-' 1193 . $Value->{ValueStop}->{ $Prefix . 'StopMonth' } . '-' 1194 . $Value->{ValueStop}->{ $Prefix . 'StopDay' }; 1195 1196 # return search parameter structure 1197 return { 1198 Parameter => { 1199 GreaterThanEquals => $ValueStart, 1200 SmallerThanEquals => $ValueStop, 1201 }, 1202 Display => $DisplayValueStart . ' - ' . $DisplayValueStop, 1203 }; 1204 } 1205 1206 return; 1207} 1208 1209sub StatsFieldParameterBuild { 1210 my ( $Self, %Param ) = @_; 1211 1212 return { 1213 Name => $Param{DynamicFieldConfig}->{Label}, 1214 Element => 'DynamicField_' . $Param{DynamicFieldConfig}->{Name}, 1215 TimePeriodFormat => 'DateInputFormat', 1216 Block => 'Time', 1217 }; 1218} 1219 1220sub StatsSearchFieldParameterBuild { 1221 my ( $Self, %Param ) = @_; 1222 1223 my $Value = $Param{Value}; 1224 1225 # set operator 1226 my $Operator = $Param{Operator}; 1227 return {} if !$Operator; 1228 1229 return { $Operator => undef } if !$Value; 1230 1231 my $DateTimeObject = $Kernel::OM->Create( 1232 'Kernel::System::DateTime', 1233 ObjectParams => { 1234 String => $Value 1235 } 1236 ); 1237 1238 my $ToReturn = $DateTimeObject->Format( Format => '%Y-%m-%d' ); 1239 1240 # Date field is limited to full calendar days 1241 # prepare restriction getting date/time fields 1242 1243 # set end of day 1244 if ( $Operator eq 'SmallerThanEquals' ) { 1245 $ToReturn .= ' 23:59:59'; 1246 } 1247 1248 # set start of day 1249 elsif ( $Operator eq 'GreaterThanEquals' ) { 1250 $ToReturn .= ' 00:00:00'; 1251 } 1252 1253 # same values for unknown operators 1254 else { 1255 $ToReturn = $DateTimeObject->ToString(); 1256 } 1257 1258 return { 1259 $Operator => $ToReturn, 1260 }; 1261} 1262 1263sub RandomValueSet { 1264 my ( $Self, %Param ) = @_; 1265 1266 my $YearValue = int( rand(40) ) + 1_990; 1267 my $MonthValue = int( rand(9) ) + 1; 1268 my $DayValue = int( rand(10) ) + 10; 1269 1270 my $Value = $YearValue . '-0' . $MonthValue . '-' . $DayValue . ' 00:00:00'; 1271 1272 my $Success = $Self->ValueSet( 1273 %Param, 1274 Value => $Value, 1275 ); 1276 1277 if ( !$Success ) { 1278 return { 1279 Success => 0, 1280 }; 1281 } 1282 return { 1283 Success => 1, 1284 Value => $Value, 1285 }; 1286} 1287 1288sub ValueLookup { 1289 my ( $Self, %Param ) = @_; 1290 1291 my $Value = defined $Param{Key} ? $Param{Key} : ''; 1292 1293 # check if a translation is possible 1294 if ( defined $Param{LanguageObject} ) { 1295 1296 # translate value 1297 $Value = $Param{LanguageObject}->FormatTimeString( 1298 $Value, 1299 'DateFormatShort', 1300 ); 1301 } 1302 1303 return $Value; 1304} 1305 1306=begin Internal: 1307 1308=cut 1309 1310=head2 _ConvertDate2DateTime() 1311 1312Append hh:mm:ss if only the ISO date was supplied to get a full date-time string. 1313 1314 my $DateTime = $BackendObject->_ConvertDate2DateTime( 1315 '2017-01-01', 1316 ); 1317 1318Returns 1319 1320 $DataTime = '2017-01-01 00:00:00' 1321 1322=cut 1323 1324sub _ConvertDate2DateTime { 1325 my ( $Self, $Value ) = @_; 1326 1327 if ( $Value && $Value =~ m{ \A \d{4}-\d{2}-\d{2} \z }xms ) { 1328 $Value .= ' 00:00:00'; 1329 } 1330 1331 return $Value; 1332} 1333 13341; 1335 1336=end Internal: 1337 1338=head1 TERMS AND CONDITIONS 1339 1340This software is part of the OTRS project (L<https://otrs.org/>). 1341 1342This software comes with ABSOLUTELY NO WARRANTY. For details, see 1343the enclosed file COPYING for license information (GPL). If you 1344did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 1345 1346=cut 1347