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