# -- # Copyright (C) 2001-2019 OTRS AG, https://otrs.com/ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you # did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. # -- package Kernel::System::Calendar::Appointment; use strict; use warnings; use Digest::MD5; use vars qw(@ISA); use Kernel::System::VariableCheck qw(:all); use Kernel::System::EventHandler; our @ObjectDependencies = ( 'Kernel::Config', 'Kernel::System::Cache', 'Kernel::System::Calendar', 'Kernel::System::DB', 'Kernel::System::Daemon::SchedulerDB', 'Kernel::System::DateTime', 'Kernel::System::Group', 'Kernel::System::Log', 'Kernel::System::Main', 'Kernel::System::Scheduler', ); =head1 NAME Kernel::System::Calendar::Appointment - calendar appointment lib =head1 DESCRIPTION All appointment functions. =head1 PUBLIC INTERFACE =head2 new() create an object. Do not use it directly, instead use: use Kernel::System::ObjectManager; local $Kernel::OM = Kernel::System::ObjectManager->new(); my $AppointmentObject = $Kernel::OM->Get('Kernel::System::Calendar::Appointment'); =cut sub new { my ( $Type, %Param ) = @_; # allocate new hash for object my $Self = {%Param}; bless( $Self, $Type ); @ISA = qw( Kernel::System::EventHandler ); # init of event handler $Self->EventHandlerInit( Config => 'AppointmentCalendar::EventModulePost', ); $Self->{CacheType} = 'Appointment'; $Self->{CacheTTL} = 60 * 60 * 24 * 20; return $Self; } =head2 AppointmentCreate() creates a new appointment. my $AppointmentID = $AppointmentObject->AppointmentCreate( ParentID => 1, # (optional) valid ParentID for recurring appointments CalendarID => 1, # (required) valid CalendarID UniqueID => 'jwioji-fwjio', # (optional) provide desired UniqueID; if there is already existing Appointment # with same UniqueID, system will delete it Title => 'Webinar', # (required) Title Description => 'How to use Process tickets...', # (optional) Description Location => 'Straubing', # (optional) Location StartTime => '2016-01-01 16:00:00', # (required) EndTime => '2016-01-01 17:00:00', # (required) AllDay => 0, # (optional) default 0 TeamID => [ 1 ], # (optional) must be an array reference if supplied ResourceID => [ 1, 3 ], # (optional) must be an array reference if supplied Recurring => 1, # (optional) flag the appointment as recurring (parent only!) RecurringRaw => 1, # (optional) skip loop for recurring appointments (do not create occurrences!) RecurrenceType => 'Daily', # (required if Recurring) Possible "Daily", "Weekly", "Monthly", "Yearly", # "CustomWeekly", "CustomMonthly", "CustomYearly" RecurrenceFrequency => [1, 3, 5], # (required if Custom Recurring) Recurrence pattern # for CustomWeekly: 1-Mon, 2-Tue,..., 7-Sun # for CustomMonthly: 1-1st, 2-2nd,.., 31th # for CustomYearly: 1-Jan, 2-Feb,..., 12-Dec # ... RecurrenceCount => 1, # (optional) How many Appointments to create RecurrenceInterval => 2, # (optional) Repeating interval (default 1) RecurrenceUntil => '2016-01-10 00:00:00', # (optional) Until date RecurrenceID => '2016-01-10 00:00:00', # (optional) Expected start time for this occurrence RecurrenceExclude => [ # (optional) Which specific occurrences to exclude '2016-01-10 00:00:00', '2016-01-11 00:00:00', ], NotificationTime => '2016-01-01 17:00:00', # (optional) Point of time to execute the notification event NotificationTemplate => 'Custom', # (optional) Template to be used for notification point of time NotificationCustom => 'relative', # (optional) Type of the custom template notification point of time # Possible "relative", "datetime" NotificationCustomRelativeUnitCount => '12', # (optional) minutes, hours or days count for custom template NotificationCustomRelativeUnit => 'minutes', # (optional) minutes, hours or days unit for custom template NotificationCustomRelativePointOfTime => 'beforestart', # (optional) Point of execute for custom templates # Possible "beforestart", "afterstart", "beforeend", "afterend" NotificationCustomDateTime => '2016-01-01 17:00:00', # (optional) Notification date time for custom template TicketAppointmentRuleID => '9bb20ea035e7a9930652a9d82d00c725', # (optional) Ticket appointment rule ID (for ticket appointments only!) UserID => 1, # (required) UserID ); returns parent AppointmentID if successful Events: AppointmentCreate =cut sub AppointmentCreate { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(CalendarID Title StartTime EndTime UserID)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # prepare possible notification params $Self->_AppointmentNotificationPrepare( Data => \%Param, ); # if Recurring is provided, additional parameters must be present if ( $Param{Recurring} ) { my @RecurrenceTypes = ( "Daily", "Weekly", "Monthly", "Yearly", "CustomDaily", "CustomWeekly", "CustomMonthly", "CustomYearly" ); if ( !$Param{RecurrenceType} || !grep { $_ eq $Param{RecurrenceType} } @RecurrenceTypes ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "RecurrenceType invalid!", ); return; } if ( ( $Param{RecurrenceType} eq 'CustomWeekly' || $Param{RecurrenceType} eq 'CustomMonthly' || $Param{RecurrenceType} eq 'CustomYearly' ) && !$Param{RecurrenceFrequency} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "RecurrenceFrequency needed!", ); return; } } $Param{RecurrenceInterval} ||= 1; if ( $Param{UniqueID} && !$Param{ParentID} ) { my %Appointment = $Self->AppointmentGet( UniqueID => $Param{UniqueID}, CalendarID => $Param{CalendarID}, ); # delete existing appointment with same UniqueID if ( %Appointment && $Appointment{AppointmentID} ) { $Self->AppointmentDelete( AppointmentID => $Appointment{AppointmentID}, UserID => $Param{UserID}, ); } } # check ParentID if ( $Param{ParentID} && !IsInteger( $Param{ParentID} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "ParentID must be a number!", ); return; } # Check StartTime. my $StartTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{StartTime}, }, ); if ( !$StartTimeObject ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Invalid StartTime!", ); return; } # check UniqueID if ( !$Param{UniqueID} ) { $Param{UniqueID} = $Self->GetUniqueID( CalendarID => $Param{CalendarID}, StartTime => $Param{StartTime}, UserID => $Param{UserID}, ); } # Check EndTime. my $EndTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{EndTime}, }, ); if ( !$EndTimeObject ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Invalid EndTime!", ); return; } # check if array refs my %Arrays; for my $Parameter ( qw(TeamID ResourceID RecurrenceFrequency RecurrenceExclude) ) { if ( $Param{$Parameter} && @{ $Param{$Parameter} // [] } ) { if ( !IsArrayRefWithData( $Param{$Parameter} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "$Parameter not ARRAYREF!", ); return; } my @Array = @{ $Param{$Parameter} }; # remove undefined values @Array = grep { defined $_ } @Array; $Arrays{$Parameter} = join( ',', @Array ) if @Array; } } # check if numbers for my $Parameter ( qw(Recurring RecurrenceCount RecurrenceInterval) ) { if ( $Param{$Parameter} && !IsInteger( $Param{$Parameter} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "$Parameter must be a number!", ); return; } } # check RecurrenceUntil if ( $Param{RecurrenceUntil} ) { # Usually hour, minute and second = 0. In this case, take time from StartTime. $Param{RecurrenceUntil} = $Self->_TimeCheck( OriginalTime => $Param{StartTime}, Time => $Param{RecurrenceUntil}, ); my $RecurrenceUntilObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{RecurrenceUntil}, }, ); if ( !$RecurrenceUntilObject || $StartTimeObject > $RecurrenceUntilObject ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Invalid RecurrenceUntil!", ); return; } } # get db object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); my @Bind; # parent ID supplied my $ParentIDCol = my $ParentIDVal = ''; if ( $Param{ParentID} ) { $ParentIDCol = 'parent_id,'; $ParentIDVal = '?,'; push @Bind, \$Param{ParentID}; # turn off all recurring fields delete $Param{Recurring}; delete $Param{RecurrenceType}; delete $Param{RecurrenceFrequency}; delete $Param{RecurrenceCount}; delete $Param{RecurrenceInterval}; delete $Param{RecurrenceUntil}; } push @Bind, \$Param{CalendarID}, \$Param{UniqueID}, \$Param{Title}, \$Param{Description}, \$Param{Location}, \$Param{StartTime}, \$Param{EndTime}, \$Param{AllDay}, \$Arrays{TeamID}, \$Arrays{ResourceID}, \$Param{Recurring}, \$Param{RecurrenceType}, \$Arrays{RecurrenceFrequency}, \$Param{RecurrenceCount}, \$Param{RecurrenceInterval}, \$Param{RecurrenceUntil}, \$Param{RecurrenceID}, \$Arrays{RecurrenceExclude}, \$Param{NotificationDate}, \$Param{NotificationTemplate}, \$Param{NotificationCustom}, \$Param{NotificationCustomRelativeUnitCount}, \$Param{NotificationCustomRelativeUnit}, \$Param{NotificationCustomRelativePointOfTime}, \$Param{NotificationCustomDateTime}, \$Param{TicketAppointmentRuleID}, \$Param{UserID}, \$Param{UserID}; my $SQL = " INSERT INTO calendar_appointment ($ParentIDCol calendar_id, unique_id, title, description, location, start_time, end_time, all_day, team_id, resource_id, recurring, recur_type, recur_freq, recur_count, recur_interval, recur_until, recur_id, recur_exclude, notify_time, notify_template, notify_custom, notify_custom_unit_count, notify_custom_unit, notify_custom_unit_point, notify_custom_date, ticket_appointment_rule_id, create_time, create_by, change_time, change_by) VALUES ($ParentIDVal ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?) "; # create db record return if !$DBObject->Do( SQL => $SQL, Bind => \@Bind, ); my $AppointmentID; # return parent id for appointment occurrences if ( $Param{ParentID} ) { $AppointmentID = $Param{ParentID}; } # get appointment id for parent appointment else { return if !$DBObject->Prepare( SQL => ' SELECT id FROM calendar_appointment WHERE unique_id=? AND parent_id IS NULL ', Bind => [ \$Param{UniqueID} ], Limit => 1, ); while ( my @Row = $DBObject->FetchrowArray() ) { $AppointmentID = $Row[0] || ''; } # return if there is not appointment created if ( !$AppointmentID ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Can\'t get AppointmentID from INSERT!', ); return; } } # add recurring appointments if ( $Param{Recurring} && !$Param{RecurringRaw} ) { return if !$Self->_AppointmentRecurringCreate( ParentID => $AppointmentID, Appointment => \%Param, ); } my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); # clean up list methods cache $CacheObject->CleanUp( Type => $Self->{CacheType} . 'List' . $Param{CalendarID}, ); $CacheObject->CleanUp( Type => $Self->{CacheType} . 'Days' . $Param{UserID}, ); # fire event $Self->EventHandler( Event => 'AppointmentCreate', Data => { AppointmentID => $AppointmentID, CalendarID => $Param{CalendarID}, }, UserID => $Param{UserID}, ); return $AppointmentID; } =head2 AppointmentList() get a hash of Appointments. my @Appointments = $AppointmentObject->AppointmentList( CalendarID => 1, # (required) Valid CalendarID Title => '*', # (optional) Filter by title, wildcard supported Description => '*', # (optional) Filter by description, wildcard supported Location => '*', # (optional) Filter by location, wildcard supported StartTime => '2016-01-01 00:00:00', # (optional) Filter by start date EndTime => '2016-02-01 00:00:00', # (optional) Filter by end date TeamID => 1, # (optional) Filter by team ResourceID => 2, # (optional) Filter by resource Result => 'HASH', # (optional), HASH|ARRAY ); returns an array of hashes with select Appointment data or simple array of AppointmentIDs: Result => 'HASH': @Appointments = [ { AppointmentID => 1, CalendarID => 1, UniqueID => '20160101T160000-71E386@localhost', Title => 'Webinar', Description => 'How to use Process tickets...', Location => 'Straubing', StartTime => '2016-01-01 16:00:00', EndTime => '2016-01-01 17:00:00', AllDay => 0, Recurring => 1, # for recurring (parent) appointments only }, { AppointmentID => 2, ParentID => 1, # for recurred (child) appointments only CalendarID => 1, UniqueID => '20160101T180000-A78B57@localhost', Title => 'Webinar', Description => 'How to use Process tickets...', Location => 'Straubing', StartTime => '2016-01-02 16:00:00', EndTime => '2016-01-02 17:00:00', TeamID => [ 1 ], ResourceID => [ 1, 3 ], AllDay => 0, }, { AppointmentID => 3, CalendarID => 1, UniqueID => '20160101T180000-A78B57@localhost', Title => 'Webinar', Description => 'How to use Process tickets...', Location => 'Straubing', StartTime => '2016-01-02 16:00:00', EndTime => '2016-01-02 17:00:00', TimezoneID => 1, TeamID => [ 1 ], ResourceID => [ 1, 3 ], NotificationDate => '2016-01-02 16:10:00', NotificationTemplate => 'Custom', NotificationCustom => 'relative', NotificationCustomRelativeUnitCount => '10', NotificationCustomRelativeUnit => 'minutes', NotificationCustomRelativePointOfTime => 'afterstart', NotificationCustomDateTime => '2016-01-02 16:00:00', TicketAppointmentRuleID => '9bb20ea035e7a9930652a9d82d00c725', # for ticket appointments only! }, ... ]; Result => 'ARRAY': @Appointments = [ 1, 2, ... ] =cut sub AppointmentList { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(CalendarID)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # output array of hashes by default $Param{Result} = $Param{Result} || 'HASH'; # cache keys my $CacheType = $Self->{CacheType} . 'List' . $Param{CalendarID}; my $CacheKeyTitle = $Param{Title} || 'any'; my $CacheKeyDesc = $Param{Description} || 'any'; my $CacheKeyLocation = $Param{Location} || 'any'; my $CacheKeyStart = $Param{StartTime} || 'any'; my $CacheKeyEnd = $Param{EndTime} || 'any'; my $CacheKeyTeam = $Param{TeamID} || 'any'; my $CacheKeyResource = $Param{ResourceID} || 'any'; if ( defined $Param{Title} && $Param{Title} =~ /^[\*]+$/ ) { $CacheKeyTitle = 'any'; } if ( defined $Param{Description} && $Param{Description} =~ /^[\*]+$/ ) { $CacheKeyDesc = 'any'; } if ( defined $Param{Location} && $Param{Location} =~ /^[\*]+$/ ) { $CacheKeyLocation = 'any'; } my $CacheKey = "$CacheKeyTitle-$CacheKeyDesc-$CacheKeyLocation-$CacheKeyStart-$CacheKeyEnd-$CacheKeyTeam-$CacheKeyResource-$Param{Result}"; # check cache my $Data = $Kernel::OM->Get('Kernel::System::Cache')->Get( Type => $CacheType, Key => $CacheKey, ); if ( ref $Data eq 'ARRAY' ) { return @{$Data}; } my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # check time if ( $Param{StartTime} ) { my $StartTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{StartTime}, }, ); if ( !$StartTimeObject ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "StartTime invalid!", ); return; } } if ( $Param{EndTime} ) { my $EndTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{EndTime}, }, ); if ( !$EndTimeObject ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "EndTime invalid!", ); return; } } my $SQL = ' SELECT id, parent_id, calendar_id, unique_id, title, description, location, start_time, end_time, team_id, resource_id, all_day, recurring, notify_time, notify_template, notify_custom, notify_custom_unit_count, notify_custom_unit, notify_custom_unit_point, notify_custom_date, ticket_appointment_rule_id FROM calendar_appointment WHERE calendar_id=? '; my @Bind; push @Bind, \$Param{CalendarID}; # Filter title, description and location fields by using QueryCondition method, which will # return backend specific SQL statements in order to provide case insensitive match and # wildcard support. FILTER: for my $Filter (qw(Title Description Location)) { next FILTER if !$Param{$Filter}; $SQL .= ' AND ' . $DBObject->QueryCondition( Key => lc $Filter, Value => $Param{$Filter}, SearchPrefix => '*', SearchSuffix => '*', ); } if ( $Param{StartTime} && $Param{EndTime} ) { $SQL .= ' AND ( (start_time >= ? AND start_time < ?) OR (end_time > ? AND end_time <= ?) OR (start_time <= ? AND end_time >= ?) ) '; push @Bind, \$Param{StartTime}, \$Param{EndTime}, \$Param{StartTime}, \$Param{EndTime}, \$Param{StartTime}, \$Param{EndTime}; } elsif ( $Param{StartTime} && !$Param{EndTime} ) { $SQL .= ' AND end_time >= ? '; push @Bind, \$Param{StartTime}; } elsif ( !$Param{StartTime} && $Param{EndTime} ) { $SQL .= ' AND start_time <= ? '; push @Bind, \$Param{EndTime}; } $SQL .= ' ORDER BY id ASC'; # db query return if !$DBObject->Prepare( SQL => $SQL, Bind => \@Bind, ); my @Result; ROW: while ( my @Row = $DBObject->FetchrowArray() ) { # team id my @TeamID = split( ',', $Row[9] // '' ); if ( $Param{TeamID} ) { next ROW if !grep { $_ == $Param{TeamID} } @TeamID; } # resource id $Row[10] = $Row[10] ? $Row[10] : 0; my @ResourceID = $Row[10] =~ /,/ ? split( ',', $Row[10] ) : ( $Row[10] ); if ( $Param{ResourceID} ) { next ROW if !grep { $_ == $Param{ResourceID} } @ResourceID; } my %Appointment = ( AppointmentID => $Row[0], ParentID => $Row[1], CalendarID => $Row[2], UniqueID => $Row[3], Title => $Row[4], Description => $Row[5], Location => $Row[6], StartTime => $Row[7], EndTime => $Row[8], TeamID => \@TeamID, ResourceID => \@ResourceID, AllDay => $Row[11], Recurring => $Row[12], NotificationDate => $Row[13] || '', NotificationTemplate => $Row[14], NotificationCustom => $Row[15], NotificationCustomRelativeUnitCount => $Row[16], NotificationCustomRelativeUnit => $Row[17], NotificationCustomRelativePointOfTime => $Row[18], NotificationCustomDateTime => $Row[19] || '', TicketAppointmentRuleID => $Row[20], ); push @Result, \%Appointment; } # if Result was ARRAY, output only IDs if ( $Param{Result} eq 'ARRAY' ) { my @ResultList; for my $Appointment (@Result) { push @ResultList, $Appointment->{AppointmentID}; } @Result = @ResultList; } # cache $Kernel::OM->Get('Kernel::System::Cache')->Set( Type => $CacheType, Key => $CacheKey, Value => \@Result, TTL => $Self->{CacheTTL}, ); return @Result; } =head2 AppointmentDays() get a hash of days with Appointments in all user calendars. my %AppointmentDays = $AppointmentObject->AppointmentDays( StartTime => '2016-01-01 00:00:00', # (optional) Filter by start date EndTime => '2016-02-01 00:00:00', # (optional) Filter by end date UserID => 1, # (required) Valid UserID ); returns a hash with days as keys and number of Appointments as values: %AppointmentDays = { '2016-01-01' => 1, '2016-01-13' => 2, '2016-01-30' => 1, }; =cut sub AppointmentDays { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(UserID)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # cache keys my $CacheType = $Self->{CacheType} . 'Days' . $Param{UserID}; my $CacheKeyStart = $Param{StartTime} || 'any'; my $CacheKeyEnd = $Param{EndTime} || 'any'; # check time if ( $Param{StartTime} ) { my $StartTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{StartTime}, }, ); if ( !$StartTimeObject ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "StartTime invalid!", ); return; } } if ( $Param{EndTime} ) { my $EndTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{EndTime}, }, ); if ( !$EndTimeObject ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "EndTime invalid!", ); return; } } my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); # check cache my $Data = $CacheObject->Get( Type => $CacheType, Key => "$CacheKeyStart-$CacheKeyEnd", ); if ( ref $Data eq 'HASH' ) { return %{$Data}; } my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # get user groups my %GroupList = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet( UserID => $Param{UserID}, Type => 'ro', ); my @GroupIDs = sort keys %GroupList; my $SQL = " SELECT ca.start_time, ca.end_time FROM calendar_appointment ca JOIN calendar c ON ca.calendar_id = c.id WHERE c.group_id IN ( ${\(join ', ', @GroupIDs)} ) "; my @Bind; if ( $Param{StartTime} && $Param{EndTime} ) { $SQL .= 'AND ( (ca.start_time >= ? AND ca.start_time < ?) OR (ca.end_time > ? AND ca.end_time <= ?) OR (ca.start_time <= ? AND ca.end_time >= ?) ) '; push @Bind, \$Param{StartTime}, \$Param{EndTime}, \$Param{StartTime}, \$Param{EndTime}, \$Param{StartTime}, \$Param{EndTime}; } elsif ( $Param{StartTime} && !$Param{EndTime} ) { $SQL .= 'AND ca.end_time >= ? '; push @Bind, \$Param{StartTime}; } elsif ( !$Param{StartTime} && $Param{EndTime} ) { $SQL .= 'AND ca.start_time <= ? '; push @Bind, \$Param{EndTime}; } $SQL .= 'ORDER BY ca.id ASC'; # db query return if !$DBObject->Prepare( SQL => $SQL, Bind => \@Bind, ); my %Result; while ( my @Row = $DBObject->FetchrowArray() ) { my ( $StartTime, $EndTime, $StartTimeSystem, $EndTimeSystem ); # StartTime if ( $Param{StartTime} ) { $StartTime = $Row[0] lt $Param{StartTime} ? $Param{StartTime} : $Row[0]; } else { $StartTime = $Row[0]; } # EndTime if ( $Param{EndTime} ) { $EndTime = $Row[1] gt $Param{EndTime} ? $Param{EndTime} : $Row[1]; } else { $EndTime = $Row[1]; } # Get system times. my $StartTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $StartTime, }, ); my $EndTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $EndTime, }, ); for ( my $LoopTimeObject = $StartTimeObject->Clone(); $LoopTimeObject < $EndTimeObject; $LoopTimeObject->Add( Days => 1 ) ) { my $LoopTime = $LoopTimeObject->ToString(); $LoopTime =~ s/\s.*?$//gsm; if ( $Result{$LoopTime} ) { $Result{$LoopTime}++; } else { $Result{$LoopTime} = 1; } } } # cache $CacheObject->Set( Type => $CacheType, Key => "$CacheKeyStart-$CacheKeyEnd", Value => \%Result, TTL => $Self->{CacheTTL}, ); return %Result; } =head2 AppointmentGet() Get appointment data. my %Appointment = $AppointmentObject->AppointmentGet( AppointmentID => 1, # (required) # or UniqueID => '20160101T160000-71E386@localhost', # (required) will return only parent for recurring appointments CalendarID => 1, # (required) ); Returns a hash: %Appointment = ( AppointmentID => 2, ParentID => 1, # only for recurred (child) appointments CalendarID => 1, UniqueID => '20160101T160000-71E386@localhost', Title => 'Webinar', Description => 'How to use Process tickets...', Location => 'Straubing', StartTime => '2016-01-01 16:00:00', EndTime => '2016-01-01 17:00:00', AllDay => 0, TeamID => [ 1 ], ResourceID => [ 1, 3 ], Recurring => 1, RecurrenceType => 'Daily', RecurrenceFrequency => 1, RecurrenceCount => 1, RecurrenceInterval => 2, RecurrenceUntil => '2016-01-10 00:00:00', RecurrenceID => '2016-01-10 00:00:00', RecurrenceExclude => [ '2016-01-10 00:00:00', '2016-01-11 00:00:00', ], NotificationTime => '2016-01-01 17:0:00', NotificationTemplate => 'Custom', NotificationCustomUnitCount => '12', NotificationCustomUnit => 'minutes', NotificationCustomUnitPointOfTime => 'beforestart', TicketAppointmentRuleID => '9bb20ea035e7a9930652a9d82d00c725', # for ticket appointments only! CreateTime => '2016-01-01 00:00:00', CreateBy => 2, ChangeTime => '2016-01-01 00:00:00', ChangeBy => 2, ); =cut sub AppointmentGet { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{AppointmentID} && !( $Param{UniqueID} && $Param{CalendarID} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need AppointmentID or UniqueID and CalendarID!", ); return; } my $Data; my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); if ( $Param{AppointmentID} ) { # check cache $Data = $CacheObject->Get( Type => $Self->{CacheType}, Key => $Param{AppointmentID}, ); } if ( ref $Data eq 'HASH' ) { return %{$Data}; } # needed objects my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); my @Bind; my $SQL = ' SELECT id, parent_id, calendar_id, unique_id, title, description, location, start_time, end_time, all_day, team_id, resource_id, recurring, recur_type, recur_freq, recur_count, recur_interval, recur_until, recur_id, recur_exclude, notify_time, notify_template, notify_custom, notify_custom_unit_count, notify_custom_unit, notify_custom_unit_point, notify_custom_date, ticket_appointment_rule_id, create_time, create_by, change_time, change_by FROM calendar_appointment WHERE '; if ( $Param{AppointmentID} ) { $SQL .= 'id=? '; push @Bind, \$Param{AppointmentID}; } else { $SQL .= 'unique_id=? AND calendar_id=? AND parent_id IS NULL '; push @Bind, \$Param{UniqueID}, \$Param{CalendarID}; } # db query return if !$DBObject->Prepare( SQL => $SQL, Bind => \@Bind, Limit => 1, ); my %Result; while ( my @Row = $DBObject->FetchrowArray() ) { # team id my @TeamID = split( ',', $Row[10] // '' ); # resource id my @ResourceID = split( ',', $Row[11] // '0' ); # recurrence frequency my @RecurrenceFrequency = $Row[14] ? split( ',', $Row[14] ) : undef; # recurrence exclude my @RecurrenceExclude = $Row[19] ? split( ',', $Row[19] ) : undef; $Result{AppointmentID} = $Row[0]; $Result{ParentID} = $Row[1]; $Result{CalendarID} = $Row[2]; $Result{UniqueID} = $Row[3]; $Result{Title} = $Row[4]; $Result{Description} = $Row[5]; $Result{Location} = $Row[6]; $Result{StartTime} = $Row[7]; $Result{EndTime} = $Row[8]; $Result{AllDay} = $Row[9]; $Result{TeamID} = \@TeamID; $Result{ResourceID} = \@ResourceID; $Result{Recurring} = $Row[12]; $Result{RecurrenceType} = $Row[13]; $Result{RecurrenceFrequency} = \@RecurrenceFrequency; $Result{RecurrenceCount} = $Row[15]; $Result{RecurrenceInterval} = $Row[16]; $Result{RecurrenceUntil} = $Row[17]; $Result{RecurrenceID} = $Row[18]; $Result{RecurrenceExclude} = \@RecurrenceExclude; $Result{NotificationDate} = $Row[20] || ''; $Result{NotificationTemplate} = $Row[21] || ''; $Result{NotificationCustom} = $Row[22] || ''; $Result{NotificationCustomRelativeUnitCount} = $Row[23] || 0; $Result{NotificationCustomRelativeUnit} = $Row[24] || ''; $Result{NotificationCustomRelativePointOfTime} = $Row[25] || ''; $Result{NotificationCustomDateTime} = $Row[26] || ''; $Result{TicketAppointmentRuleID} = $Row[27]; $Result{CreateTime} = $Row[28]; $Result{CreateBy} = $Row[29]; $Result{ChangeTime} = $Row[30]; $Result{ChangeBy} = $Row[31]; } if ( $Param{AppointmentID} ) { # cache $CacheObject->Set( Type => $Self->{CacheType}, Key => $Param{AppointmentID}, Value => \%Result, TTL => $Self->{CacheTTL}, ); } return %Result; } =head2 AppointmentUpdate() updates an existing appointment. my $Success = $AppointmentObject->AppointmentUpdate( AppointmentID => 2, # (required) CalendarID => 1, # (required) Valid CalendarID Title => 'Webinar', # (required) Title Description => 'How to use Process tickets...', # (optional) Description Location => 'Straubing', # (optional) Location StartTime => '2016-01-01 16:00:00', # (required) EndTime => '2016-01-01 17:00:00', # (required) AllDay => 0, # (optional) Default 0 Team => 1, # (optional) ResourceID => [ 1, 3 ], # (optional) must be an array reference if supplied Recurring => 1, # (optional) flag the appointment as recurring (parent only!) RecurrenceType => 'Daily', # (required if Recurring) Possible "Daily", "Weekly", "Monthly", "Yearly", # "CustomWeekly", "CustomMonthly", "CustomYearly" RecurrenceFrequency => 1, # (required if Custom Recurring) Recurrence pattern # for CustomWeekly: 1-Mon, 2-Tue,..., 7-Sun # for CustomMonthly: 1-Jan, 2-Feb,..., 12-Dec # ... RecurrenceCount => 1, # (optional) How many Appointments to create RecurrenceInterval => 2, # (optional) Repeating interval (default 1) RecurrenceUntil => '2016-01-10 00:00:00', # (optional) Until date NotificationTime => '2016-01-01 17:00:00', # (optional) Point of time to execute the notification event NotificationTemplate => 'Custom', # (optional) Template to be used for notification point of time NotificationCustom => 'relative', # (optional) Type of the custom template notification point of time # Possible "relative", "datetime" NotificationCustomRelativeUnitCount => '12', # (optional) minutes, hours or days count for custom template NotificationCustomRelativeUnit => 'minutes', # (optional) minutes, hours or days unit for custom template NotificationCustomRelativePointOfTime => 'beforestart', # (optional) Point of execute for custom templates # Possible "beforestart", "afterstart", "beforeend", "afterend" NotificationCustomDateTime => '2016-01-01 17:00:00', # (optional) Notification date time for custom template TicketAppointmentRuleID => '9bb20ea035e7a9930652a9d82d00c725', # (optional) Ticket appointment rule ID (for ticket appointments only!) UserID => 1, # (required) UserID ); returns 1 if successful: $Success = 1; Events: AppointmentUpdate =cut sub AppointmentUpdate { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(AppointmentID CalendarID Title StartTime EndTime UserID)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # prepare possible notification params my $Success = $Self->_AppointmentNotificationPrepare( Data => \%Param, ); # if Recurring is provided, additional parameters must be present if ( $Param{Recurring} ) { my @RecurrenceTypes = ( "Daily", "Weekly", "Monthly", "Yearly", "CustomDaily", "CustomWeekly", "CustomMonthly", "CustomYearly" ); if ( !$Param{RecurrenceType} || !grep { $_ eq $Param{RecurrenceType} } @RecurrenceTypes ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "RecurrenceType invalid!", ); return; } if ( ( $Param{RecurrenceType} eq 'CustomWeekly' || $Param{RecurrenceType} eq 'CustomMonthly' || $Param{RecurrenceType} eq 'CustomYearly' ) && !$Param{RecurrenceFrequency} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "RecurrenceFrequency needed!", ); return; } } $Param{RecurrenceInterval} ||= 1; # Check StartTime. my $StartTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{StartTime}, }, ); if ( !$StartTimeObject ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "StartTime invalid!", ); return; } # Check EndTime. my $EndTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{EndTime}, }, ); if ( !$EndTimeObject ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "EndTime invalid!", ); return; } # needed objects my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); # check if array refs my %Arrays; for my $Parameter ( qw(TeamID ResourceID RecurrenceFrequency) ) { if ( $Param{$Parameter} && @{ $Param{$Parameter} // [] } ) { if ( !IsArrayRefWithData( $Param{$Parameter} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "$Parameter not ARRAYREF!", ); return; } my @Array = @{ $Param{$Parameter} }; # remove undefined values @Array = grep { defined $_ } @Array; $Arrays{$Parameter} = join( ',', @Array ) if @Array; } } # check if numbers for my $Parameter ( qw(Recurring RecurrenceCount RecurrenceInterval) ) { if ( $Param{$Parameter} && !IsInteger( $Param{$Parameter} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "$Parameter must be a number!", ); return; } } # check RecurrenceUntil if ( $Param{RecurrenceUntil} ) { # Usually hour, minute and second = 0. In this case, take time from StartTime. $Param{RecurrenceUntil} = $Self->_TimeCheck( OriginalTime => $Param{StartTime}, Time => $Param{RecurrenceUntil}, ); my $RecurrenceUntilObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{RecurrenceUntil}, }, ); if ( !$RecurrenceUntilObject || $StartTimeObject > $RecurrenceUntilObject ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "RecurrenceUntil invalid!", ); return; } } # get previous CalendarID my $PreviousCalendarID = $Self->_AppointmentGetCalendarID( AppointmentID => $Param{AppointmentID}, ); # set recurrence exclude list my @RecurrenceExclude = @{ $Param{RecurrenceExclude} // [] }; # get RecurrenceID my $RecurrenceID = $Self->_AppointmentGetRecurrenceID( AppointmentID => $Param{AppointmentID}, ); # use exclude list to flag the recurring occurrence as updated if ($RecurrenceID) { @RecurrenceExclude = ($RecurrenceID); } # reset exclude list if recurrence is turned off elsif ( !$Param{Recurring} ) { @RecurrenceExclude = (); } # remove undefined values @RecurrenceExclude = grep { defined $_ } @RecurrenceExclude; # serialize data my $RecurrenceExclude = join( ',', @RecurrenceExclude ) || undef; # delete existing recurred appointments my $DeleteSuccess = $Self->_AppointmentRecurringDelete( ParentID => $Param{AppointmentID}, ); if ( !$DeleteSuccess ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Unable to delete recurring Appointment!", ); return; } # update appointment my $SQL = ' UPDATE calendar_appointment SET calendar_id=?, title=?, description=?, location=?, start_time=?, end_time=?, all_day=?, team_id=?, resource_id=?, recurring=?, recur_type=?, recur_freq=?, recur_count=?, recur_interval=?, recur_until=?, recur_exclude=?, notify_time=?, notify_template=?, notify_custom=?, notify_custom_unit_count=?, notify_custom_unit=?, notify_custom_unit_point=?, notify_custom_date=?, ticket_appointment_rule_id=?, change_time=current_timestamp, change_by=? WHERE id=? '; # update db record return if !$Kernel::OM->Get('Kernel::System::DB')->Do( SQL => $SQL, Bind => [ \$Param{CalendarID}, \$Param{Title}, \$Param{Description}, \$Param{Location}, \$Param{StartTime}, \$Param{EndTime}, \$Param{AllDay}, \$Arrays{TeamID}, \$Arrays{ResourceID}, \$Param{Recurring}, \$Param{RecurrenceType}, \$Arrays{RecurrenceFrequency}, \$Param{RecurrenceCount}, \$Param{RecurrenceInterval}, \$Param{RecurrenceUntil}, \$RecurrenceExclude, \$Param{NotificationDate}, \$Param{NotificationTemplate}, \$Param{NotificationCustom}, \$Param{NotificationCustomRelativeUnitCount}, \$Param{NotificationCustomRelativeUnit}, \$Param{NotificationCustomRelativePointOfTime}, \$Param{NotificationCustomDateTime}, \$Param{TicketAppointmentRuleID}, \$Param{UserID}, \$Param{AppointmentID}, ], ); # add recurred appointments again if ( $Param{Recurring} ) { return if !$Self->_AppointmentRecurringCreate( ParentID => $Param{AppointmentID}, Appointment => \%Param, ); } # delete cache $CacheObject->Delete( Type => $Self->{CacheType}, Key => $Param{AppointmentID}, ); # clean up list methods cache my @CalendarIDs = ( $Param{CalendarID} ); push @CalendarIDs, $PreviousCalendarID if $PreviousCalendarID ne $Param{CalendarID}; for my $CalendarID (@CalendarIDs) { $CacheObject->CleanUp( Type => $Self->{CacheType} . 'List' . $CalendarID, ); } $CacheObject->CleanUp( Type => $Self->{CacheType} . 'Days' . $Param{UserID}, ); # fire event $Self->EventHandler( Event => 'AppointmentUpdate', Data => { AppointmentID => $Param{AppointmentID}, CalendarID => $Param{CalendarID}, }, UserID => $Param{UserID}, ); return 1; } =head2 AppointmentDelete() deletes an existing appointment. my $Success = $AppointmentObject->AppointmentDelete( AppointmentID => 1, # (required) UserID => 1, # (required) ); returns 1 if successful: $Success = 1; Events: AppointmentDelete =cut sub AppointmentDelete { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(AppointmentID UserID)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # needed objects my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); # get CalendarID my $CalendarID = $Self->_AppointmentGetCalendarID( AppointmentID => $Param{AppointmentID}, ); # check user's permissions for this calendar my $Permission = $Kernel::OM->Get('Kernel::System::Calendar')->CalendarPermissionGet( CalendarID => $CalendarID, UserID => $Param{UserID}, ); my @RequiredPermissions = ( 'create', 'rw' ); if ( !grep { $Permission eq $_ } @RequiredPermissions ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "User($Param{UserID}) has no permission to delete Appointment($Param{AppointmentID})!", ); return; } my %Appointment = $Self->AppointmentGet( AppointmentID => $Param{AppointmentID}, ); # save exclusion info to parent appointment if ( $Appointment{ParentID} && $Appointment{RecurrenceID} ) { $Self->_AppointmentRecurringExclude( ParentID => $Appointment{ParentID}, RecurrenceID => $Appointment{RecurrenceID}, ); } # delete recurring appointments my $DeleteRecurringSuccess = $Self->_AppointmentRecurringDelete( ParentID => $Param{AppointmentID}, ); if ( !$DeleteRecurringSuccess ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Recurring appointments couldn\'t be deleted!', ); return; } # delete appointment my $SQL = ' DELETE FROM calendar_appointment WHERE id=? '; # delete db record return if !$Kernel::OM->Get('Kernel::System::DB')->Do( SQL => $SQL, Bind => [ \$Param{AppointmentID}, ], ); # Fire event. $Self->EventHandler( Event => 'AppointmentDelete', Data => { AppointmentID => $Param{AppointmentID}, CalendarID => $CalendarID, }, UserID => $Param{UserID}, ); # delete cache $CacheObject->Delete( Type => $Self->{CacheType}, Key => $Param{AppointmentID}, ); # clean up list methods cache $CacheObject->CleanUp( Type => $Self->{CacheType} . 'List' . $CalendarID, ); $CacheObject->CleanUp( Type => $Self->{CacheType} . 'Days' . $Param{UserID}, ); return 1; } =head2 AppointmentDeleteOccurrence() deletes a single recurring appointment occurrence. my $Success = $AppointmentObject->AppointmentDeleteOccurrence( UniqueID => '20160101T160000-71E386@localhost', # (required) RecurrenceID => '2016-01-10 00:00:00', # (required) UserID => 1, # (required) ); returns 1 if successful: $Success = 1; =cut sub AppointmentDeleteOccurrence { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(UniqueID CalendarID RecurrenceID UserID)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # get db object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # db query return if !$DBObject->Prepare( SQL => ' SELECT id FROM calendar_appointment WHERE unique_id=? AND calendar_id=? AND recur_id=?', Bind => [ \$Param{UniqueID}, \$Param{CalendarID}, \$Param{RecurrenceID} ], Limit => 1, ); my %Appointment; # get additional info while ( my @Row = $DBObject->FetchrowArray() ) { $Appointment{AppointmentID} = $Row[0]; } return if !%Appointment; # delete db record return if !$DBObject->Do( SQL => 'DELETE FROM calendar_appointment WHERE id=?', Bind => [ \$Appointment{AppointmentID} ], Limit => 1, ); # get cache object my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); # delete cache $CacheObject->Delete( Type => $Self->{CacheType}, Key => $Appointment{AppointmentID}, ); # clean up list methods cache $CacheObject->CleanUp( Type => $Self->{CacheType} . 'List' . $Param{CalendarID}, ); $CacheObject->CleanUp( Type => $Self->{CacheType} . 'Days' . $Param{UserID}, ); return 1; } =head2 GetUniqueID() Returns UniqueID containing appointment start time, random hash and system C. my $UniqueID = $AppointmentObject->GetUniqueID( CalendarID => 1, # (required) StartTime => '2016-01-01 00:00:00', # (required) UserID => 1, # (required) ); $UniqueID = '20160101T000000-B9909D@localhost'; =cut sub GetUniqueID { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(CalendarID StartTime UserID)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # calculate a hash my $RandomString = $Kernel::OM->Get('Kernel::System::Main')->GenerateRandomString( Length => 32 ); my $String = "$Param{CalendarID}-$RandomString-$Param{UserID}"; my $Digest = unpack( 'N', Digest::MD5->new()->add($String)->digest() ); my $DigestHex = sprintf( '%x', $Digest ); my $Hash = uc( sprintf( "%.6s", $DigestHex ) ); # Prepare start timestamp for UniqueID. my $StartTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{StartTime}, }, ); return if !$StartTimeObject; my $StartTimeStrg = $StartTimeObject->ToString(); $StartTimeStrg =~ s/[-:]//g; $StartTimeStrg =~ s/\s/T/; # get system FQDN my $FQDN = $Kernel::OM->Get('Kernel::Config')->Get('FQDN'); # return UniqueID return "$StartTimeStrg-$Hash\@$FQDN"; } =head2 AppointmentUpcomingGet() Get appointment data for upcoming appointment start or end. my @UpcomingAppointments = $AppointmentObject->AppointmentUpcomingGet( Timestamp => '2016-08-02 03:59:00', # get appointments for the related notification timestamp ); Returns appointment data of AppointmentGet(). =cut sub AppointmentUpcomingGet { my ( $Self, %Param ) = @_; # get current timestamp my $CurrentTimestamp; # create needed sql query based on the current or a given timestamp my $SQL = 'SELECT id, parent_id, calendar_id, unique_id FROM calendar_appointment '; if ( $Param{Timestamp} ) { $CurrentTimestamp = $Param{Timestamp}; $SQL .= "WHERE notify_time = ? "; } else { $CurrentTimestamp = $Kernel::OM->Create('Kernel::System::DateTime')->ToString(); $SQL .= "WHERE notify_time >= ? "; } $SQL .= 'ORDER BY notify_time ASC'; my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # db query return if !$DBObject->Prepare( SQL => $SQL, Bind => [ \$CurrentTimestamp ], ); my @ResultRaw; while ( my @Row = $DBObject->FetchrowArray() ) { my %UpcomingAppointment; $UpcomingAppointment{AppointmentID} = $Row[0]; $UpcomingAppointment{ParentID} = $Row[1]; $UpcomingAppointment{CalendarID} = $Row[2]; $UpcomingAppointment{UniqueID} = $Row[3]; push @ResultRaw, \%UpcomingAppointment; } my @Results; APPOINTMENTDATA: for my $AppointmentData (@ResultRaw) { next APPOINTMENTDATA if !IsHashRefWithData($AppointmentData); next APPOINTMENTDATA if !$AppointmentData->{CalendarID}; next APPOINTMENTDATA if !$AppointmentData->{AppointmentID}; my %Appointment = $Self->AppointmentGet( %{$AppointmentData} ); push @Results, \%Appointment; } return @Results; } =head2 AppointmentFutureTasksDelete() Delete all calendar appointment future tasks. my $Success = $AppointmentObject->AppointmentFutureTasksDelete(); returns: True if future task deletion was successful, otherwise false. =cut sub AppointmentFutureTasksDelete { my ( $Self, %Param ) = @_; # get a local scheduler db object my $SchedulerObject = $Kernel::OM->Get('Kernel::System::Scheduler'); # get a list of already stored future tasks my @FutureTaskList = $SchedulerObject->FutureTaskList( Type => 'CalendarAppointment', ); # flush obsolete future tasks if ( IsArrayRefWithData( \@FutureTaskList ) ) { FUTURETASK: for my $FutureTask (@FutureTaskList) { next FUTURETASK if !$FutureTask; next FUTURETASK if !IsHashRefWithData($FutureTask); my $Success = $SchedulerObject->FutureTaskDelete( TaskID => $FutureTask->{TaskID}, ); if ( !$Success ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Could not delete future task with id $FutureTask->{TaskID}!", ); return; } } } return 1; } =head2 AppointmentFutureTasksUpdate() Update OTRS daemon future task list for upcoming appointments. my $Success = $AppointmentObject->AppointmentFutureTasksUpdate(); returns: True if future task update was successful, otherwise false. =cut sub AppointmentFutureTasksUpdate { my ( $Self, %Param ) = @_; # get appointment data for upcoming appointments my @UpcomingAppointments = $Self->AppointmentUpcomingGet(); # check for no upcoming appointments if ( !IsArrayRefWithData( \@UpcomingAppointments ) ) { # flush obsolete future tasks my $Success = $Self->AppointmentFutureTasksDelete(); if ( !$Success ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Could not delete appointment future tasks!', ); return; } return 1; } # get a local scheduler db object my $SchedulerObject = $Kernel::OM->Get('Kernel::System::Scheduler'); # get a list of already stored future tasks my @FutureTaskList = $SchedulerObject->FutureTaskList( Type => 'CalendarAppointment', ); # check for invalid task count (just one task max allowed) if ( scalar @FutureTaskList > 1 ) { # flush obsolete future tasks my $Success = $Self->AppointmentFutureTasksDelete(); if ( !$Success ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Could not delete appointment future tasks!', ); return; } } # check if it is needed to update the future task list if ( IsArrayRefWithData( \@FutureTaskList ) ) { my $UpdateNeeded = 0; FUTURETASK: for my $FutureTask (@FutureTaskList) { if ( !IsHashRefWithData($FutureTask) || !$FutureTask->{TaskID} || !$FutureTask->{ExecutionTime} ) { $UpdateNeeded = 1; last FUTURETASK; } # get the stored future task my %FutureTaskData = $Kernel::OM->Get('Kernel::System::Daemon::SchedulerDB')->FutureTaskGet( TaskID => $FutureTask->{TaskID}, ); if ( !IsHashRefWithData( \%FutureTaskData ) ) { $UpdateNeeded = 1; last FUTURETASK; } # Get date time objects of stored and upcoming times to compare. my $FutureTaskTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $FutureTaskData{Data}->{NotifyTime}, }, ); my $UpcomingAppointmentTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $UpcomingAppointments[0]->{NotificationDate}, }, ); # Do nothing if the upcoming notification time equals the stored value. if ( $UpcomingAppointmentTimeObject != $FutureTaskTimeObject ) { $UpdateNeeded = 1; last FUTURETASK; } } if ($UpdateNeeded) { # flush obsolete future tasks my $Success = $Self->AppointmentFutureTasksDelete(); if ( !$Success ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Could not delete appointment future tasks!', ); return; } } else { return 1; } } # schedule new future tasks for notification actions my $TaskID = $SchedulerObject->TaskAdd( ExecutionTime => $UpcomingAppointments[0]->{NotificationDate}, Name => 'AppointmentNotification', Type => 'CalendarAppointment', Data => { NotifyTime => $UpcomingAppointments[0]->{NotificationDate}, }, ); if ( !$TaskID ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Could not schedule future task for AppointmentID $UpcomingAppointments[0]->{AppointmentID}!", ); return; } return 1; } =head2 _AppointmentNotificationPrepare() Prepare appointment notification data. my $Success = $AppointmentObject->_AppointmentNotificationPrepare(); returns: True if preparation was successful, otherwise false. =cut sub _AppointmentNotificationPrepare { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(Data)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # reset notification data if needed if ( !$Param{Data}->{NotificationTemplate} ) { for my $PossibleParam ( qw( NotificationDate NotificationTemplate NotificationCustom NotificationCustomRelativeUnitCount NotificationCustomRelativeUnit NotificationCustomRelativePointOfTime NotificationCustomDateTime ) ) { $Param{Data}->{$PossibleParam} = undef; } } # prepare possible notification params for my $PossibleParam ( qw( NotificationTemplate NotificationCustom NotificationCustomRelativeUnit NotificationCustomRelativePointOfTime ) ) { $Param{Data}->{$PossibleParam} ||= ''; } # special check for relative unit count as it can be zero # (empty and negative values will be treated as zero to avoid errors) if ( !IsNumber( $Param{Data}->{NotificationCustomRelativeUnitCount} ) || $Param{Data}->{NotificationCustomRelativeUnitCount} <= 0 ) { $Param{Data}->{NotificationCustomRelativeUnitCount} = 0; } # set empty datetime strings to undef for my $PossibleParam (qw(NotificationDate NotificationCustomDateTime)) { $Param{Data}->{$PossibleParam} ||= undef; } return if !$Param{Data}->{NotificationTemplate}; # # template Start # if ( $Param{Data}->{NotificationTemplate} eq 'Start' ) { # setup the appointment start date as notification date $Param{Data}->{NotificationDate} = $Param{Data}->{StartTime}; } # # template time before start # elsif ( $Param{Data}->{NotificationTemplate} ne 'Custom' && IsNumber( $Param{Data}->{NotificationTemplate} ) && $Param{Data}->{NotificationTemplate} > 0 ) { return if !IsNumber( $Param{Data}->{NotificationTemplate} ); # offset template (before start datetime) used my $Offset = $Param{Data}->{NotificationTemplate}; # Get date time object of appointment start time. my $StartTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{Data}->{StartTime}, }, ); # Subtract offset in seconds for new notification date time. $StartTimeObject->Subtract( Seconds => $Offset, ); $Param{Data}->{NotificationDate} = $StartTimeObject->ToString(); } # # template Custom # else { # Compute date of custom relative input. if ( $Param{Data}->{NotificationCustom} eq 'relative' ) { my $CustomUnitCount = $Param{Data}->{NotificationCustomRelativeUnitCount}; my $CustomUnit = $Param{Data}->{NotificationCustomRelativeUnit}; my $CustomUnitPoint = $Param{Data}->{NotificationCustomRelativePointOfTime}; # setup the count to compute for the offset my %UnitOffsetCompute = ( minutes => 60, hours => 3600, days => 86400, ); my $NotificationLocalTimeObject; # Compute from start time. if ( $CustomUnitPoint eq 'beforestart' || $CustomUnitPoint eq 'afterstart' ) { $NotificationLocalTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{Data}->{StartTime}, }, ); } # Compute from end time. elsif ( $CustomUnitPoint eq 'beforeend' || $CustomUnitPoint eq 'afterend' ) { $NotificationLocalTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{Data}->{EndTime}, }, ); } # Not supported point of time. else { return; } # compute the offset to be used my $Offset = ( $CustomUnitCount * $UnitOffsetCompute{$CustomUnit} ); # save the newly computed notification datetime string if ( $CustomUnitPoint eq 'beforestart' || $CustomUnitPoint eq 'beforeend' ) { $NotificationLocalTimeObject->Subtract( Seconds => $Offset, ); $Param{Data}->{NotificationDate} = $NotificationLocalTimeObject->ToString(); } else { $NotificationLocalTimeObject->Add( Seconds => $Offset, ); $Param{Data}->{NotificationDate} = $NotificationLocalTimeObject->ToString(); } } # Compute date of custom date/time input. elsif ( $Param{Data}->{NotificationCustom} eq 'datetime' ) { $Param{Data}->{NotificationCustom} = 'datetime'; # validation if ( !IsStringWithData( $Param{Data}->{NotificationCustomDateTime} ) ) { return; } # save the given date time values as notification datetime string (i.e. 2016-06-28 02:00:00) $Param{Data}->{NotificationDate} = $Param{Data}->{NotificationCustomDateTime}; } } if ( !IsStringWithData( $Param{Data}->{NotificationDate} ) ) { $Param{Data}->{NotificationDate} = undef; } if ( !IsStringWithData( $Param{Data}->{NotificationCustomDateTime} ) ) { $Param{Data}->{NotificationCustomDateTime} = undef; } return 1; } =head2 AppointmentNotification() Will be triggered by the OTRS daemon to fire events for appointments, that reaches it's reminder (notification) time. my $Success = $AppointmentObject->AppointmentNotification(); returns: True if notify action was successful, otherwise false. =cut sub AppointmentNotification { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(NotifyTime)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # get appointments for the related notification timestamp my @UpcomingAppointments = $Self->AppointmentUpcomingGet( Timestamp => $Param{NotifyTime}, ); return if !IsArrayRefWithData( \@UpcomingAppointments ); # sleep at least 1 second to make sure the timestamp doesn't # equals the last one for update upcoming future tasks sleep 1; UPCOMINGAPPOINTMENT: for my $UpcomingAppointment (@UpcomingAppointments) { next UPCOMINGAPPOINTMENT if !$UpcomingAppointment; next UPCOMINGAPPOINTMENT if !IsHashRefWithData($UpcomingAppointment); next UPCOMINGAPPOINTMENT if !$UpcomingAppointment->{AppointmentID}; # fire event $Self->EventHandler( Event => 'AppointmentNotification', Data => { AppointmentID => $UpcomingAppointment->{AppointmentID}, CalendarID => $UpcomingAppointment->{CaledarID}, }, UserID => 1, ); } return 1; } =begin Internal: =cut sub _AppointmentRecurringCreate { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(ParentID Appointment)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } my $StartTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{Appointment}->{StartTime}, }, ); my $EndTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{Appointment}->{EndTime}, }, ); my @RecurrenceExclude = @{ $Param{Appointment}->{RecurrenceExclude} // [] }; # remove undefined values @RecurrenceExclude = grep { defined $_ } @RecurrenceExclude; # reset the parameter for occurrences $Param{Appointment}->{RecurrenceExclude} = undef; my $OriginalStartTimeObject = $StartTimeObject->Clone(); my $OriginalEndTimeObject = $EndTimeObject->Clone(); my $Step = 0; # until ... if ( $Param{Appointment}->{RecurrenceUntil} ) { my $RecurrenceUntilObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{Appointment}->{RecurrenceUntil}, }, ); UNTIL_TIME: while ( $StartTimeObject <= $RecurrenceUntilObject ) { $Step += $Param{Appointment}->{RecurrenceInterval}; # calculate recurring times $StartTimeObject = $Self->_CalculateRecurrenceTime( Appointment => $Param{Appointment}, Step => $Step, OriginalTime => $OriginalStartTimeObject, CurrentTime => $StartTimeObject, ); last UNTIL_TIME if !$StartTimeObject; last UNTIL_TIME if $StartTimeObject > $RecurrenceUntilObject; $EndTimeObject = $StartTimeObject->Clone(); $EndTimeObject->Add( Seconds => $OriginalEndTimeObject->Delta( DateTimeObject => $OriginalStartTimeObject )->{AbsoluteSeconds}, ); my $StartTime = $StartTimeObject->ToString(); my $EndTime = $EndTimeObject->ToString(); # Bugfix: on some systems with older perl version system might calculate timezone difference. $StartTime = $Self->_TimeCheck( OriginalTime => $Param{Appointment}->{StartTime}, Time => $StartTime, ); $EndTime = $Self->_TimeCheck( OriginalTime => $Param{Appointment}->{EndTime}, Time => $EndTime, ); # skip excluded appointments next UNTIL_TIME if grep { $StartTime eq $_ } @RecurrenceExclude; $Self->AppointmentCreate( %{ $Param{Appointment} }, ParentID => $Param{ParentID}, StartTime => $StartTime, EndTime => $EndTime, RecurrenceID => $StartTime, ); } } # for ... time(s) elsif ( $Param{Appointment}->{RecurrenceCount} ) { COUNT: for ( 1 .. $Param{Appointment}->{RecurrenceCount} - 1 ) { $Step += $Param{Appointment}->{RecurrenceInterval}; # calculate recurring times $StartTimeObject = $Self->_CalculateRecurrenceTime( Appointment => $Param{Appointment}, Step => $Step, OriginalTime => $OriginalStartTimeObject, CurrentTime => $StartTimeObject, ); last COUNT if !$StartTimeObject; $EndTimeObject = $StartTimeObject->Clone(); $EndTimeObject->Add( Seconds => $OriginalEndTimeObject->Delta( DateTimeObject => $OriginalStartTimeObject )->{AbsoluteSeconds}, ); my $StartTime = $StartTimeObject->ToString(); my $EndTime = $EndTimeObject->ToString(); # Bugfix: on some systems with older perl version system might calculate timezone difference. $StartTime = $Self->_TimeCheck( OriginalTime => $Param{Appointment}->{StartTime}, Time => $StartTime, ); $EndTime = $Self->_TimeCheck( OriginalTime => $Param{Appointment}->{EndTime}, Time => $EndTime, ); # skip excluded appointments next COUNT if grep { $StartTime eq $_ } @RecurrenceExclude; $Self->AppointmentCreate( %{ $Param{Appointment} }, ParentID => $Param{ParentID}, StartTime => $StartTime, EndTime => $EndTime, RecurrenceID => $StartTime, ); } } return 1; } sub _AppointmentRecurringDelete { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(ParentID)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # delete recurring appointments my $SQL = ' DELETE FROM calendar_appointment WHERE parent_id=? '; # delete db record return if !$Kernel::OM->Get('Kernel::System::DB')->Do( SQL => $SQL, Bind => [ \$Param{ParentID}, ], ); return 1; } sub _AppointmentRecurringExclude { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(ParentID RecurrenceID)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # db query return if !$DBObject->Prepare( SQL => 'SELECT recur_exclude FROM calendar_appointment WHERE id=?', Bind => [ \$Param{ParentID} ], ); # get existing exclusions my @RecurrenceExclude; while ( my @Row = $DBObject->FetchrowArray() ) { @RecurrenceExclude = split( ',', $Row[0] ) if $Row[0]; } push @RecurrenceExclude, $Param{RecurrenceID}; @RecurrenceExclude = sort @RecurrenceExclude; # join into string my $RecurrenceExclude; if (@RecurrenceExclude) { $RecurrenceExclude = join( ',', @RecurrenceExclude ); } # update db record return if !$DBObject->Do( SQL => 'UPDATE calendar_appointment SET recur_exclude=? WHERE id=?', Bind => [ \$RecurrenceExclude, \$Param{ParentID} ], ); # delete cache $CacheObject->Delete( Type => $Self->{CacheType}, Key => $Param{ParentID}, ); return 1; } sub _AppointmentGetCalendarID { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(AppointmentID)) { if ( !defined $Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # sql query my $SQL = 'SELECT calendar_id FROM calendar_appointment WHERE id=?'; my @Bind = ( \$Param{AppointmentID} ); # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # start query return if !$DBObject->Prepare( SQL => $SQL, Bind => \@Bind, Limit => 1, ); my $CalendarID; while ( my @Row = $DBObject->FetchrowArray() ) { $CalendarID = $Row[0]; } return $CalendarID; } sub _AppointmentGetRecurrenceID { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(AppointmentID)) { if ( !defined $Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # sql query my $SQL = 'SELECT recur_id FROM calendar_appointment WHERE id=?'; my @Bind = ( \$Param{AppointmentID} ); # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # start query return if !$DBObject->Prepare( SQL => $SQL, Bind => \@Bind, Limit => 1, ); my $RecurrenceID; while ( my @Row = $DBObject->FetchrowArray() ) { $RecurrenceID = $Row[0]; } return $RecurrenceID; } sub _CalculateRecurrenceTime { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(Appointment Step OriginalTime CurrentTime)) { if ( !defined $Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } my $OriginalTimeObject = $Param{OriginalTime}; # We will modify this object throughout the function. my $CurrentTimeObject = $Param{CurrentTime}; if ( $Param{Appointment}->{RecurrenceType} eq 'Daily' ) { # Add one day. $CurrentTimeObject->Add( Days => 1, ); } elsif ( $Param{Appointment}->{RecurrenceType} eq 'Weekly' ) { # Add 7 days. $CurrentTimeObject->Add( Days => 7, ); } elsif ( $Param{Appointment}->{RecurrenceType} eq 'Monthly' ) { my $TempTimeObject = $OriginalTimeObject->Clone(); # Remember start day. my $StartDay = $TempTimeObject->Get()->{Day}; # Add months based on current step. $TempTimeObject->Add( Months => $Param{Step}, ); # Get end day. my $EndDay = $TempTimeObject->Get()->{Day}; # Check if month doesn't have enough days (for example: January 31 + 1 month = March 1). if ( $StartDay != $EndDay ) { $TempTimeObject->Subtract( Days => $EndDay, ); } $CurrentTimeObject = $TempTimeObject->Clone(); } elsif ( $Param{Appointment}->{RecurrenceType} eq 'Yearly' ) { my $TempTimeObject = $OriginalTimeObject->Clone(); # Remember start day. my $StartDay = $TempTimeObject->Get()->{Day}; # Add years based on current step. $TempTimeObject->Add( Years => $Param{Step}, ); # Get end day. my $EndDay = $TempTimeObject->Get()->{Day}; # Check if month doesn't have enough days (for example: January 31 + 1 month = March 1). if ( $StartDay != $EndDay ) { $TempTimeObject->Subtract( Days => $EndDay, ); } $CurrentTimeObject = $TempTimeObject->Clone(); } elsif ( $Param{Appointment}->{RecurrenceType} eq 'CustomDaily' ) { # Add number of days. $CurrentTimeObject->Add( Days => $Param{Appointment}->{RecurrenceInterval}, ); } elsif ( $Param{Appointment}->{RecurrenceType} eq 'CustomWeekly' ) { # this block covers following use case: # each n-th Monday and Friday my $Found; # loop up to 7*n times (7 days in week * frequency) LOOP: for ( my $Counter = 0; $Counter < 7 * $Param{Appointment}->{RecurrenceInterval}; $Counter++ ) { # Add one day. $CurrentTimeObject->Add( Days => 1, ); my $CWDiff = $Self->_CWDiff( CurrentTime => $CurrentTimeObject, OriginalTime => $OriginalTimeObject, ); next LOOP if $CWDiff % $Param{Appointment}->{RecurrenceInterval}; my $WeekDay = $CurrentTimeObject->Get()->{DayOfWeek}; # check if SystemTime match requirements if ( grep { $WeekDay == $_ } @{ $Param{Appointment}->{RecurrenceFrequency} } ) { $Found = 1; last LOOP; } } return if !$Found; } elsif ( $Param{Appointment}->{RecurrenceType} eq 'CustomMonthly' ) { # Occurs every 2nd month on 5th, 10th and 15th day my $Found; # loop through each day (max one year), and check if day matches. DAY: for ( my $Counter = 0; $Counter < 31 * 366; $Counter++ ) { # Add one day. $CurrentTimeObject->Add( Days => 1, ); # Skip month if needed next DAY if ( $CurrentTimeObject->Get()->{Month} - $OriginalTimeObject->Get()->{Month} ) % $Param{Appointment}->{RecurrenceInterval}; # next day if this day should be skipped next DAY if !grep { $CurrentTimeObject->Get()->{Day} == $_ } @{ $Param{Appointment}->{RecurrenceFrequency} }; $Found = 1; last DAY; } return if !$Found; } elsif ( $Param{Appointment}->{RecurrenceType} eq 'CustomYearly' ) { # this block covers following use case: # Occurs each 3th year, January 18th and March 18th my $Found; my $RecurrenceUntilObject; if ( $Param{Appointment}->{RecurrenceUntil} ) { $RecurrenceUntilObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Param{Appointment}->{RecurrenceUntil}, }, ); } my $NextDayObject = $CurrentTimeObject->Clone(); $NextDayObject->Add( Days => 1, ); MONTH: for ( my $Counter = 1;; $Counter++ ) { my $TempTimeObject = $OriginalTimeObject->Clone(); # remember start day my $StartDay = $TempTimeObject->Get()->{Day}; $TempTimeObject->Add( Months => $Counter, ); # get end day my $EndDay = $TempTimeObject->Get()->{Day}; # check if month doesn't have enough days (for example: january 31 + 1 month = march 01) if ( $StartDay != $EndDay ) { $TempTimeObject->Subtract( Days => $EndDay, ); } $CurrentTimeObject = $TempTimeObject->Clone(); # skip this time, since it was already checked next MONTH if $CurrentTimeObject < $NextDayObject; # check loop conditions (according to Until / ) if ($RecurrenceUntilObject) { last MONTH if $CurrentTimeObject > $RecurrenceUntilObject; } else { last MONTH if $Counter > 12 * $Param{Appointment}->{RecurrenceInterval} * $Param{Appointment}->{RecurrenceCount}; } # check if year is OK next MONTH if ( $CurrentTimeObject->Get()->{Year} - $OriginalTimeObject->Get()->{Year} ) % $Param{Appointment}->{RecurrenceInterval}; # next month if this month should be skipped next MONTH if !grep { $CurrentTimeObject->Get()->{Month} == $_ } @{ $Param{Appointment}->{RecurrenceFrequency} }; $Found = 1; last MONTH; } return if !$Found; } else { return; } return $CurrentTimeObject; } =head2 _TimeCheck() Check if Time and OriginalTime have same hour, minute and second value, and return timestamp with values (hour, minute and second) as in Time. my $Result = $Self->_TimeCheck( OriginalTime => '2016-01-01 00:01:00', # (required) Time => '2016-02-01 00:02:00', # (required) ); Returns: $Result = '2016-02-01 00:01:00'; =cut sub _TimeCheck { my ( $Self, %Param ) = @_; for my $Needed (qw(OriginalTime Time)) { if ( !defined $Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } my $Result = ''; $Param{OriginalTime} =~ /(.*?)\s(.*?)$/; my $OriginalDate = $1; my $OriginalTime = $2; $Param{Time} =~ /(.*?)\s(.*?)$/; my $Date = $1; $Result = "$Date $OriginalTime"; return $Result; } =head2 _CWDiff() Returns how many calendar weeks has passed between two unix times. my $CWDiff = $Self->_CWDiff( CurrentTime => $CurrentTimeObject, (required) Date time object with current time OriginalTime => $OriginalTimeObject, (required) Date time object with original time ); returns: $CWDiff = 5; =cut sub _CWDiff { my ( $Self, %Param ) = @_; for my $Needed (qw(CurrentTime OriginalTime)) { if ( !defined $Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } my $OriginalTimeObject = $Param{OriginalTime}; my $CurrentTimeObject = $Param{CurrentTime}; my $StartYear = $OriginalTimeObject->Get()->{Year}; my $EndYear = $CurrentTimeObject->Get()->{Year}; my $Result = $CurrentTimeObject->{CPANDateTimeObject}->week_number() - $OriginalTimeObject->{CPANDateTimeObject}->week_number(); # If date is end of the year and date CW starts with 1, we need to include additional year. if ( $Result < 0 && $CurrentTimeObject->Get()->{Day} == 31 && $CurrentTimeObject->Get()->{Month} == 12 ) { $EndYear++; } for my $Year ( $StartYear .. $EndYear - 1 ) { my $CW = 0; my $Day = 31; while ( $CW < 50 ) { # To get how many CW's are in this year, we set temporary date to 31-dec. my $DateTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => "$Year-12-$Day 23:59:00", }, ); $CW = $DateTimeObject->{CPANDateTimeObject}->week_number(); $Day--; } $Result += $CW; } return $Result; } 1; =end Internal: =head1 TERMS AND CONDITIONS This software is part of the OTRS project (L). This software comes with ABSOLUTELY NO WARRANTY. For details, see the enclosed file COPYING for license information (GPL). If you did not receive this file, see L. =cut