Files
scripts/Perl OTRS/Kernel/System/Calendar.pm
2024-10-14 00:08:40 +02:00

2245 lines
63 KiB
Perl

# --
# 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;
use strict;
use warnings;
use Digest::MD5;
use MIME::Base64 ();
use Kernel::System::EventHandler;
use Kernel::Language qw(Translatable);
use Kernel::System::VariableCheck qw(:all);
use vars qw(@ISA);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::Cache',
'Kernel::System::Calendar::Appointment',
'Kernel::System::DynamicField',
'Kernel::System::Encode',
'Kernel::System::Group',
'Kernel::System::DB',
'Kernel::System::Log',
'Kernel::System::Main',
'Kernel::System::Queue',
'Kernel::System::Storable',
'Kernel::System::Ticket',
'Kernel::System::Valid',
);
=head1 NAME
Kernel::System::Calendar - calendar lib
=head1 DESCRIPTION
All calendar 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 $CalendarObject = $Kernel::OM->Get('Kernel::System::Calendar');
=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} = 'Calendar';
$Self->{CacheTTL} = 60 * 60 * 24 * 20;
return $Self;
}
=head2 CalendarCreate()
creates a new calendar for given user.
my %Calendar = $CalendarObject->CalendarCreate(
CalendarName => 'Meetings', # (required) Personal calendar name
GroupID => 3, # (required) GroupID
Color => '#FF7700', # (required) Color in hexadecimal RGB notation
UserID => 4, # (required) UserID
TicketAppointments => [ # (optional) Ticket appointments, array ref of hashes
{
StartDate => 'FirstResponse',
EndDate => 'Plus_5',
QueueID => [ 2 ],
SearchParams => {
Title => 'This is a title',
Types => 'This is a type',
},
},
],
ValidID => 1, # (optional) Default is 1.
);
returns Calendar hash if successful:
%Calendar = (
CalendarID => 2,
GroupID => 3,
CalendarName => 'Meetings',
CreateTime => '2016-01-01 08:00:00',
CreateBy => 4,
ChangeTime => '2016-01-01 08:00:00',
ChangeBy => 4,
ValidID => 1,
);
Events:
CalendarCreate
=cut
sub CalendarCreate {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(CalendarName GroupID Color UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# check color
if ( !( $Param{Color} =~ /#[A-F0-9]{3,6}/i ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Color must be in hexadecimal RGB notation, eg. #FFFFFF.',
);
return;
}
# reset ticket appointments
if ( !( scalar @{ $Param{TicketAppointments} // [] } ) ) {
$Param{TicketAppointments} = undef;
}
# make it uppercase for the sake of consistency
$Param{Color} = uc $Param{Color};
my $ValidID = defined $Param{ValidID} ? $Param{ValidID} : 1;
my %Calendar = $Self->CalendarGet(
CalendarName => $Param{CalendarName},
);
# return if calendar with same name already exists
return if %Calendar;
# create salt string
my $SaltString = $Kernel::OM->Get('Kernel::System::Main')->GenerateRandomString(
Length => 64,
);
# serialize and encode ticket appointment data
my $TicketAppointments;
if ( $Param{TicketAppointments} ) {
$TicketAppointments = $Kernel::OM->Get('Kernel::System::Storable')->Serialize(
Data => $Param{TicketAppointments},
);
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput($TicketAppointments);
$TicketAppointments = MIME::Base64::encode_base64($TicketAppointments);
}
my $SQL = '
INSERT INTO calendar
(group_id, name, salt_string, color, ticket_appointments, create_time, create_by,
change_time, change_by, valid_id)
VALUES (?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?, ?)
';
# create db record
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => $SQL,
Bind => [
\$Param{GroupID}, \$Param{CalendarName}, \$SaltString, \$Param{Color},
\$TicketAppointments, \$Param{UserID}, \$Param{UserID}, \$ValidID
],
);
%Calendar = $Self->CalendarGet(
CalendarName => $Param{CalendarName},
UserID => $Param{UserID},
);
return if !%Calendar;
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
# cache value
$CacheObject->Set(
Type => $Self->{CacheType},
Key => $Calendar{CalendarID},
Value => \%Calendar,
TTL => $Self->{CacheTTL},
);
# reset CalendarList
$CacheObject->CleanUp(
Type => 'CalendarList',
);
# fire event
$Self->EventHandler(
Event => 'CalendarCreate',
Data => {
%Calendar,
},
UserID => $Param{UserID},
);
return %Calendar;
}
=head2 CalendarGet()
Get calendar by name or id.
my %Calendar = $CalendarObject->CalendarGet(
CalendarName => 'Meetings', # (required) Calendar name
# or
CalendarID => 4, # (required) CalendarID
UserID => 2, # (optional) UserID - System will check if user has access to calendar if provided
);
Returns Calendar data:
%Calendar = (
CalendarID => 2,
GroupID => 3,
CalendarName => 'Meetings',
Color => '#FF7700',
TicketAppointments => [
{
StartDate => 'FirstResponse',
EndDate => 'Plus_5',
QueueID => [ 2 ],
SearchParams => {
Title => 'This is a title',
Types => 'This is a type',
},
},
],
CreateTime => '2016-01-01 08:00:00',
CreateBy => 1,
ChangeTime => '2016-01-01 08:00:00',
ChangeBy => 1,
ValidID => 1,
);
=cut
sub CalendarGet {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{CalendarID} && !$Param{CalendarName} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need CalendarID or CalendarName!",
);
return;
}
my %Calendar;
if ( $Param{CalendarID} ) {
# check if value is cached
my $Data = $Kernel::OM->Get('Kernel::System::Cache')->Get(
Type => $Self->{CacheType},
Key => $Param{CalendarID},
);
if ( IsHashRefWithData($Data) ) {
%Calendar = %{$Data};
}
}
if ( !%Calendar ) {
# create db object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
my $SQL = '
SELECT id, group_id, name, color, ticket_appointments, create_time, create_by,
change_time, change_by, valid_id
FROM calendar
WHERE
';
my @Bind;
if ( $Param{CalendarID} ) {
$SQL .= '
id=?
';
push @Bind, \$Param{CalendarID};
}
else {
$SQL .= '
name=?
';
push @Bind, \$Param{CalendarName};
}
# db query
return if !$DBObject->Prepare(
SQL => $SQL,
Bind => \@Bind,
Limit => 1,
);
while ( my @Row = $DBObject->FetchrowArray() ) {
# decode and deserialize ticket appointment data
my $TicketAppointments;
if ( $Row[4] ) {
my $DecodedData = MIME::Base64::decode_base64( $Row[4] );
$TicketAppointments = $Kernel::OM->Get('Kernel::System::Storable')->Deserialize(
Data => $DecodedData,
);
$TicketAppointments = undef if ref $TicketAppointments ne 'ARRAY';
}
$Calendar{CalendarID} = $Row[0];
$Calendar{GroupID} = $Row[1];
$Calendar{CalendarName} = $Row[2];
$Calendar{Color} = $Row[3];
$Calendar{TicketAppointments} = $TicketAppointments;
$Calendar{CreateTime} = $Row[5];
$Calendar{CreateBy} = $Row[6];
$Calendar{ChangeTime} = $Row[7];
$Calendar{ChangeBy} = $Row[8];
$Calendar{ValidID} = $Row[9];
}
if ( $Param{CalendarID} ) {
# cache
$Kernel::OM->Get('Kernel::System::Cache')->Set(
Type => $Self->{CacheType},
Key => $Param{CalendarID},
Value => \%Calendar,
TTL => $Self->{CacheTTL},
);
}
if ( $Param{UserID} && $Calendar{GroupID} ) {
# get user groups
my %GroupList = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet(
UserID => $Param{UserID},
Type => 'ro',
);
if ( !grep { $Calendar{GroupID} == $_ } keys %GroupList ) {
%Calendar = ();
}
}
}
return %Calendar;
}
=head2 CalendarList()
Get calendar list.
my @Result = $CalendarObject->CalendarList(
UserID => 4, # (optional) For permission check
Permission => 'rw', # (optional) Required permission (default ro)
ValidID => 1, # (optional) Default 0.
# 0 - All states
# 1 - All valid
# 2 - All invalid
# 3 - All temporary invalid
);
Returns:
@Result = [
{
CalendarID => 2,
GroupID => 3,
CalendarName => 'Meetings',
Color => '#FF7700',
CreateTime => '2016-01-01 08:00:00',
CreateBy => 3,
ChangeTime => '2016-01-01 08:00:00',
ChangeBy => 3,
ValidID => 1,
},
{
CalendarID => 3,
GroupID => 3,
CalendarName => 'Customer presentations',
Color => '#BB00BB',
CreateTime => '2016-01-01 08:00:00',
CreateBy => 3,
ChangeTime => '2016-01-01 08:00:00',
ChangeBy => 3,
ValidID => 0,
},
...
];
=cut
sub CalendarList {
my ( $Self, %Param ) = @_;
# Make different cache type for list (so we can clear cache by this value)
my $CacheType = 'CalendarList';
my $CacheKeyUser = $Param{UserID} || 'all-user-ids';
my $CacheKeyValid = $Param{ValidID} || 'all-valid-ids';
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
# get cached value if exists
my $Data = $CacheObject->Get(
Type => $CacheType,
Key => "$CacheKeyUser-$CacheKeyValid",
);
if ( !IsArrayRefWithData($Data) ) {
# create needed objects
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
my $SQL = '
SELECT id, group_id, name, color, create_time, create_by, change_time, change_by,
valid_id
FROM calendar
WHERE 1=1
';
my @Bind;
if ( $Param{ValidID} ) {
$SQL .= ' AND valid_id=? ';
push @Bind, \$Param{ValidID};
}
$SQL .= 'ORDER BY id ASC';
# db query
return if !$DBObject->Prepare(
SQL => $SQL,
Bind => \@Bind,
);
my @Result;
while ( my @Row = $DBObject->FetchrowArray() ) {
my %Calendar;
$Calendar{CalendarID} = $Row[0];
$Calendar{GroupID} = $Row[1];
$Calendar{CalendarName} = $Row[2];
$Calendar{Color} = $Row[3];
$Calendar{CreateTime} = $Row[4];
$Calendar{CreateBy} = $Row[5];
$Calendar{ChangeTime} = $Row[6];
$Calendar{ChangeBy} = $Row[7];
$Calendar{ValidID} = $Row[8];
push @Result, \%Calendar;
}
# cache data
$CacheObject->Set(
Type => $CacheType,
Key => "$CacheKeyUser-$CacheKeyValid",
Value => \@Result,
TTL => $Self->{CacheTTL},
);
$Data = \@Result;
}
if ( $Param{UserID} ) {
# get user groups
my %GroupList = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet(
UserID => $Param{UserID},
Type => $Param{Permission} || 'ro',
);
my @Result;
for my $Item ( @{$Data} ) {
if ( grep { $Item->{GroupID} == $_ } keys %GroupList ) {
push @Result, $Item;
}
}
$Data = \@Result;
}
return @{$Data};
}
=head2 CalendarUpdate()
updates an existing calendar.
my $Success = $CalendarObject->CalendarUpdate(
CalendarID => 1, # (required) CalendarID
GroupID => 2, # (required) Calendar group
CalendarName => 'Meetings', # (required) Personal calendar name
Color => '#FF9900', # (required) Color in hexadecimal RGB notation
UserID => 4, # (required) UserID (who made update)
ValidID => 1, # (required) ValidID
TicketAppointments => [ # (optional) Ticket appointments, array ref of hashes
{
StartDate => 'FirstResponse',
EndDate => 'Plus_5',
QueueID => [ 2 ],
SearchParams => {
Title => 'This is a title',
Types => 'This is a type',
},
},
],
);
Returns 1 if successful.
Events:
CalendarUpdate
=cut
sub CalendarUpdate {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(CalendarID GroupID CalendarName Color UserID ValidID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# check color
if ( !( $Param{Color} =~ /#[A-F0-9]{3,6}/i ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Color must be in hexadecimal RGB notation, eg. #FFFFFF.',
);
return;
}
# reset ticket appointments
if ( !( scalar @{ $Param{TicketAppointments} // [] } ) ) {
$Param{TicketAppointments} = undef;
}
# make it uppercase for the sake of consistency
$Param{Color} = uc $Param{Color};
# serialize and encode ticket appointment data
my $TicketAppointments;
if ( $Param{TicketAppointments} ) {
$TicketAppointments = $Kernel::OM->Get('Kernel::System::Storable')->Serialize(
Data => $Param{TicketAppointments},
);
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput($TicketAppointments);
$TicketAppointments = MIME::Base64::encode_base64($TicketAppointments);
}
my $SQL = '
UPDATE calendar
SET group_id=?, name=?, color=?, ticket_appointments=?, change_time=current_timestamp,
change_by=?, valid_id=?
';
my @Bind;
push @Bind, \$Param{GroupID}, \$Param{CalendarName}, \$Param{Color}, \$TicketAppointments,
\$Param{UserID}, \$Param{ValidID};
$SQL .= '
WHERE id=?
';
push @Bind, \$Param{CalendarID};
# create db record
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => $SQL,
Bind => \@Bind,
);
# get cache object
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
# clear cache
$CacheObject->CleanUp(
Type => 'CalendarList',
);
$CacheObject->Delete(
Type => $Self->{CacheType},
Key => $Param{CalendarID},
);
# fire event
$Self->EventHandler(
Event => 'CalendarUpdate',
Data => {
CalendarID => $Param{CalendarID},
},
UserID => $Param{UserID},
);
return 1;
}
=head2 CalendarImport()
import a calendar
my $Success = $CalendarObject->CalendarImport(
Data => {
CalendarData => {
CalendarID => 2,
GroupID => 3,
CalendarName => 'Meetings',
Color => '#FF7700',
ValidID => 1,
},
AppointmentData => {
{
AppointmentID => 2,
ParentID => 1,
CalendarID => 1,
UniqueID => '20160101T160000-71E386@localhost',
...
},
...
},
},
OverwriteExistingEntities => 0, # (optional) Overwrite existing calendar and appointments, default: 0
# Calendar with same name will be overwritten
# Appointments with same UniqueID in existing calendar will be overwritten
UserID => 1,
);
returns 1 if successful
=cut
sub CalendarImport {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(Data UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
return if !IsHashRefWithData( $Param{Data} );
return if !IsHashRefWithData( $Param{Data}->{CalendarData} );
if (
defined $Param{Data}->{CalendarData}->{TicketAppointments}
&& IsArrayRefWithData( $Param{Data}->{CalendarData}->{TicketAppointments} )
)
{
# Get queue create permissions for the user.
my %UserGroups = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserGet(
UserID => $Param{UserID},
Type => 'create',
);
my @ValidIDs = $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet();
my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
# Queue field in ticket appointments is mandatory, check if it's present and valid.
for my $Rule ( @{ $Param{Data}->{CalendarData}->{TicketAppointments} } ) {
if ( defined $Rule->{QueueID} && IsArrayRefWithData( $Rule->{QueueID} ) ) {
QUEUE_ID:
for my $QueueID ( sort @{ $Rule->{QueueID} || [] } ) {
my %QueueData = $QueueObject->QueueGet( ID => $QueueID );
if (
!grep { $_ eq $QueueData{ValidID} } @ValidIDs
|| !$UserGroups{ $QueueData{GroupID} }
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Invalid queue ID $QueueID in ticket appointment rule or no permissions!",
);
return;
}
}
}
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need queue ID in ticket appointment rules!',
);
return;
}
}
}
# check for an existing calendar
my %ExistingCalendar = $Self->CalendarGet(
CalendarName => $Param{Data}->{CalendarData}->{CalendarName},
);
my $CalendarID;
# create new calendar
if ( !IsHashRefWithData( \%ExistingCalendar ) ) {
my %Calendar = $Self->CalendarCreate(
%{ $Param{Data}->{CalendarData} },
UserID => $Param{UserID},
);
return if !$Calendar{CalendarID};
$CalendarID = $Calendar{CalendarID};
}
# update existing calendar
else {
if ( $Param{OverwriteExistingEntities} ) {
my $Success = $Self->CalendarUpdate(
%{ $Param{Data}->{CalendarData} },
CalendarID => $ExistingCalendar{CalendarID},
UserID => $Param{UserID},
);
return if !$Success;
}
$CalendarID = $ExistingCalendar{CalendarID};
}
# import appointments
if ( $CalendarID && IsArrayRefWithData( $Param{Data}->{AppointmentData} ) ) {
my $AppointmentObject = $Kernel::OM->Get('Kernel::System::Calendar::Appointment');
my $AppointmentID;
APPOINTMENT:
for my $Appointment ( @{ $Param{Data}->{AppointmentData} } ) {
# add to existing calendar
$Appointment->{CalendarID} = $CalendarID;
# create new appointment if NOT overwriting existing entities
$Appointment->{UniqueID} = undef if !$Param{OverwriteExistingEntities};
# skip adding automatic recurring occurrences
if ( $Appointment->{Recurring} ) {
$Appointment->{RecurringRaw} = 1;
}
# set parent id to last appointment id
if ( $Appointment->{ParentID} ) {
$Appointment->{ParentID} = $AppointmentID;
}
$AppointmentID = $AppointmentObject->AppointmentCreate(
%{$Appointment},
UserID => $Param{UserID},
);
return if !$AppointmentID;
}
}
return 1;
}
=head2 CalendarExport()
export a calendar
my %Data = $CalendarObject->CalendarExport(
CalendarID => 2,
UserID => 1,
}
returns calendar hash with data:
%Data = (
CalendarData => {
CalendarID => 2,
GroupID => 3,
CalendarName => 'Meetings',
Color => '#FF7700',
ValidID => 1,
},
AppointmentData => (
{
AppointmentID => 2,
ParentID => 1,
CalendarID => 1,
UniqueID => '20160101T160000-71E386@localhost',
...
},
...
),
);
=cut
sub CalendarExport {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(CalendarID UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# get calendar data
my %CalendarData = $Self->CalendarGet(
CalendarID => $Param{CalendarID},
UserID => $Param{UserID},
);
return if !IsHashRefWithData( \%CalendarData );
# get appointment object
my $AppointmentObject = $Kernel::OM->Get('Kernel::System::Calendar::Appointment');
# get list of appointments
my @Appointments = $AppointmentObject->AppointmentList(
CalendarID => $Param{CalendarID},
Result => 'ARRAY',
);
my @AppointmentData;
APPOINTMENTID:
for my $AppointmentID (@Appointments) {
my %Appointment = $AppointmentObject->AppointmentGet(
AppointmentID => $AppointmentID,
);
next APPOINTMENTID if !%Appointment;
next APPOINTMENTID if $Appointment{TicketAppointmentRuleID};
push @AppointmentData, \%Appointment;
}
my %Result = (
CalendarData => \%CalendarData,
AppointmentData => \@AppointmentData,
);
return %Result;
}
=head2 CalendarPermissionGet()
Get permission level for given CalendarID and UserID.
my $Permission = $CalendarObject->CalendarPermissionGet(
CalendarID => 1, # (required) CalendarID
UserID => 4, # (required) UserID
);
Returns:
$Permission = 'rw'; # 'ro', 'rw', ...
=cut
sub CalendarPermissionGet {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(CalendarID UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# make sure super user has read/write permission
return 'rw' if $Param{UserID} eq 1;
my %Calendar = $Self->CalendarGet(
CalendarID => $Param{CalendarID},
);
my $Result = '';
my $GroupObject = $Kernel::OM->Get('Kernel::System::Group');
TYPE:
for my $Type (qw(ro move_into create rw)) {
my %GroupData = $GroupObject->PermissionUserGet(
UserID => $Param{UserID},
Type => $Type,
);
if ( $GroupData{ $Calendar{GroupID} } ) {
$Result = $Type;
}
else {
last TYPE;
}
}
return $Result;
}
=head2 TicketAppointmentProcessTicket()
Handle the automatic ticket appointments for the ticket.
$CalendarObject->TicketAppointmentProcessTicket(
TicketID => 1,
);
This method does not have return value.
=cut
sub TicketAppointmentProcessTicket {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{TicketID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need TicketID!',
);
return;
}
# get all valid calendars
my @Calendars = $Self->CalendarList(
ValidID => 1,
);
return if !@Calendars;
# get ticket appointment types
my %TicketAppointmentTypes = $Self->TicketAppointmentTypesGet();
# get ticket object
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
# go through all calendars with defined ticket appointments
CALENDAR:
for my $Calendar (@Calendars) {
my %CalendarData = $Self->CalendarGet(
CalendarID => $Calendar->{CalendarID},
);
next CALENDAR if !$CalendarData{TicketAppointments};
TICKET_APPOINTMENTS:
for my $TicketAppointments ( @{ $CalendarData{TicketAppointments} } ) {
# check appointment types
for my $Field (qw(StartDate EndDate)) {
# allow special time presets for EndDate
if ( $Field ne 'EndDate' && !( $TicketAppointments->{$Field} =~ /^Plus_/ ) ) {
# skip if ticket appointment type is invalid
if ( !$TicketAppointmentTypes{ $TicketAppointments->{$Field} } ) {
next TICKET_APPOINTMENTS;
}
}
}
# check if ticket satisfies the search filter from the ticket appointment rule
# pass all configured parameters to ticket search, including ticket id
my $Filtered = $TicketObject->TicketSearch(
Result => 'COUNT',
TicketID => $Param{TicketID},
QueueIDs => $TicketAppointments->{QueueID},
UserID => 1,
%{ $TicketAppointments->{SearchParam} // {} },
);
# ticket was found
if ($Filtered) {
# process ticket appointment rule
$Self->TicketAppointmentProcessRule(
CalendarID => $Calendar->{CalendarID},
Config => \%TicketAppointmentTypes,
Rule => $TicketAppointments,
TicketID => $Param{TicketID},
);
}
# ticket was not found
else {
# remove any existing ticket appointment
$Self->TicketAppointmentDelete(
CalendarID => $Calendar->{CalendarID},
TicketID => $Param{TicketID},
RuleID => $TicketAppointments->{RuleID},
);
}
}
}
if ( $Kernel::OM->Get('Kernel::Config')->Get('Debug') ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'debug',
Message => "Processed ticket appointments for ticket $Param{TicketID}.",
);
}
return;
}
=head2 TicketAppointmentProcessCalendar()
Handle the automatic ticket appointments for the calendar.
my %Result = $CalendarObject->TicketAppointmentProcessCalendar(
CalendarID => 1,
);
Returns log of processed tickets and rules:
%Result = (
Process => [
{
TicketID => 1,
RuleID => '9bb20ea035e7a9930652a9d82d00c725',
Success => 1,
},
{
TicketID => 2,
RuleID => '9bb20ea035e7a9930652a9d82d00c725',
Success => 1,
},
],
Cleanup => [
{
RuleID => 'b272a035ed82d65a927a99300e00c9b5',
Success => 1,
},
],
);
=cut
sub TicketAppointmentProcessCalendar {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{CalendarID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need CalendarID!',
);
return;
}
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
# Get calendar configuration.
my %Calendar = $Self->CalendarGet(
CalendarID => $Param{CalendarID},
);
if ( !%Calendar ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Could not find calendar $Param{CalendarID}!",
);
return;
}
# Get ticket appointment types
my %TicketAppointmentTypes = $Self->TicketAppointmentTypesGet();
my @Process;
my @Cleanup;
my %RuleIDLookup;
# Check ticket appointments config.
if ( $Calendar{TicketAppointments} && IsArrayRefWithData( $Calendar{TicketAppointments} ) ) {
# Get active rule IDs from the calendar configuration.
%RuleIDLookup = map { $_->{RuleID} => 1 } @{ $Calendar{TicketAppointments} };
TICKET_APPOINTMENTS:
for my $TicketAppointments ( @{ $Calendar{TicketAppointments} } ) {
# Check appointment types.
for my $Field (qw(StartDate EndDate)) {
# Allow special time presets for EndDate.
if ( $Field ne 'EndDate' && !( $TicketAppointments->{$Field} =~ /^Plus_/ ) ) {
# Skip if ticket appointment type is invalid.
if ( !$TicketAppointmentTypes{ $TicketAppointments->{$Field} } ) {
next TICKET_APPOINTMENTS;
}
}
}
# Get previously created ticket appointments for this rule.
my %OldAppointments = $Self->_TicketAppointmentList(
CalendarID => $Param{CalendarID},
RuleID => $TicketAppointments->{RuleID},
);
# Find tickets that match search filter
my @TicketIDs = $TicketObject->TicketSearch(
Result => 'ARRAY',
QueueIDs => $TicketAppointments->{QueueID},
UserID => 1,
%{ $TicketAppointments->{SearchParam} // {} },
);
# Process each ticket based on ticket appointment rule.
TICKETID:
for my $TicketID ( sort @TicketIDs ) {
my $Success = $Self->TicketAppointmentProcessRule(
CalendarID => $Param{CalendarID},
Config => \%TicketAppointmentTypes,
Rule => $TicketAppointments,
TicketID => $TicketID,
);
push @Process, {
TicketID => $TicketID,
RuleID => $TicketAppointments->{RuleID},
Success => $Success,
};
}
# Remove previously created ticket appointments if they don't match the rule anymore.
OLDTICKETID:
for my $OldTicketID ( sort keys %OldAppointments ) {
next OLDTICKETID if grep { $OldTicketID == $_ } @TicketIDs;
my $Success = $Self->TicketAppointmentDelete(
AppointmentID => $OldAppointments{$OldTicketID},
TicketID => $OldTicketID,
CalendarID => $Param{CalendarID},
RuleID => $TicketAppointments->{RuleID},
);
push @Cleanup, {
AppointmentID => $OldAppointments{$OldTicketID},
TicketID => $OldTicketID,
RuleID => $TicketAppointments->{RuleID},
Success => $Success,
};
}
}
}
my @RuleIDs = $Self->TicketAppointmentRuleIDsGet(
CalendarID => $Param{CalendarID},
);
# Remove ticket appointments for missing rules.
for my $RuleID (@RuleIDs) {
if ( !$RuleIDLookup{$RuleID} ) {
my $Success = $Self->TicketAppointmentDelete(
CalendarID => $Param{CalendarID},
RuleID => $RuleID,
);
push @Cleanup, {
RuleID => $RuleID,
Success => $Success,
};
}
}
if ( $Kernel::OM->Get('Kernel::Config')->Get('Debug') ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'debug',
Message => "Processed ticket appointments for calendar $Param{CalendarID}.",
);
}
return (
Process => \@Process,
Cleanup => \@Cleanup,
);
}
=head2 TicketAppointmentProcessRule()
Process the ticket appointment rule and create, update or delete appointment if necessary.
my $Success = $CalendarObject->TicketAppointmentProcessRule(
CalendarID => 1,
Config => {
DynamicField_TestDate => {
Module => 'Kernel::System::Calendar::Ticket::DynamicField',
},
...
},
Rule => {
StartDate => 'DynamicField_TestDate',
EndDate => 'Plus_5',
QueueID => [ 2 ],
RuleID => '9bb20ea035e7a9930652a9d82d00c725',
SearchParams => {
Title => 'Welcome*',
},
},
TicketID => 1,
);
Returns 1 if successful.
=cut
sub TicketAppointmentProcessRule {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(CalendarID Config Rule TicketID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
return if !IsHashRefWithData( $Param{Config} );
return if !IsHashRefWithData( $Param{Rule} );
my $Error;
my $AppointmentType;
my %AppointmentData;
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
# get start and end time values
for my $Field (qw(StartDate EndDate)) {
my $Type = $Param{Rule}->{$Field};
# appointment fields are named differently
my $AppointmentField = $Field;
$AppointmentField =~ s/Date$/Time/;
# check if we are dealing with a registered type
if ( $Param{Config}->{$Type} && $Param{Config}->{$Type}->{Module} ) {
my $GenericModule = $Param{Config}->{$Type}->{Module};
# get the time value via the module method
if ( $MainObject->Require($GenericModule) ) {
$AppointmentData{$AppointmentField} = $GenericModule->new( %{$Self} )->GetTime(
Type => $Type,
TicketID => $Param{TicketID},
);
$Error = 1 if !$AppointmentData{$AppointmentField};
}
}
# time presets are valid only for end time and existing start time
elsif ( $Field eq 'EndDate' && $AppointmentData{StartTime} ) {
if ( $Type =~ /^Plus_([0-9]+)$/ ) {
my $Preset = int $1;
# Get start time.
my $StartTimeObject = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
String => $AppointmentData{StartTime},
},
);
# Calculate end time using preset value.
my $EndTimeObject = $StartTimeObject->Clone();
$EndTimeObject->Add(
Minutes => $Preset,
);
$AppointmentData{EndTime} = $EndTimeObject->ToString();
}
else {
$Error = 1;
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Invalid time preset: $Type",
);
}
}
# unknown type
else {
$Error = 1;
}
}
# Prevent end time before start time.
if ( $AppointmentData{StartTime} && $AppointmentData{EndTime} ) {
my $StartTimeObject = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
String => $AppointmentData{StartTime},
},
);
my $EndTimeObject = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
String => $AppointmentData{EndTime},
},
);
if ( $EndTimeObject < $StartTimeObject ) {
$AppointmentData{EndTime} = $AppointmentData{StartTime};
}
}
# get appointment title
if ( !$Error ) {
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $TicketHook = $ConfigObject->Get('Ticket::Hook');
my $TicketHookDivider = $ConfigObject->Get('Ticket::HookDivider');
my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet(
TicketID => $Param{TicketID},
DynamicFields => 0,
UserID => 1,
);
$AppointmentData{Title} = "[$TicketHook$TicketHookDivider$Ticket{TicketNumber}] $Ticket{Title}";
}
my $Success;
# check if ticket appointment already exists
my $AppointmentID = $Self->_TicketAppointmentGet(
CalendarID => $Param{CalendarID},
TicketID => $Param{TicketID},
RuleID => $Param{Rule}->{RuleID},
);
# ticket appointment was found
if ($AppointmentID) {
# delete the ticket appointment, if error was raised
if ($Error) {
$Success = $Self->TicketAppointmentDelete(
CalendarID => $Param{CalendarID},
TicketID => $Param{TicketID},
RuleID => $Param{Rule}->{RuleID},
AppointmentID => $AppointmentID,
);
}
# update the ticket appointment, otherwise
else {
$Success = $Self->_TicketAppointmentUpdate(
CalendarID => $Param{CalendarID},
AppointmentID => $AppointmentID,
TicketID => $Param{TicketID},
RuleID => $Param{Rule}->{RuleID},
%AppointmentData,
);
}
}
# create ticket appointment if not found
elsif ( !$Error ) {
$Success = $Self->_TicketAppointmentCreate(
CalendarID => $Param{CalendarID},
TicketID => $Param{TicketID},
RuleID => $Param{Rule}->{RuleID},
%AppointmentData,
);
}
return $Success;
}
=head2 TicketAppointmentUpdateTicket()
Updates the ticket with data from ticket appointment.
$CalendarObject->TicketAppointmentUpdateTicket(
AppointmentID => 1,
TicketID => 1,
);
This method does not have return value.
=cut
sub TicketAppointmentUpdateTicket {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(AppointmentID TicketID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# get appointment data
my %AppointmentData = $Kernel::OM->Get('Kernel::System::Calendar::Appointment')->AppointmentGet(
AppointmentID => $Param{AppointmentID},
);
# stop if not ticket appointment
return if !$AppointmentData{TicketAppointmentRuleID};
# get ticket appointment rule
my $Rule = $Self->TicketAppointmentRuleGet(
CalendarID => $AppointmentData{CalendarID},
RuleID => $AppointmentData{TicketAppointmentRuleID},
);
return if !IsHashRefWithData($Rule);
# get ticket appointment types
my %TicketAppointmentTypes = $Self->TicketAppointmentTypesGet();
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
# process start and end time values
for my $Field (qw(StartDate EndDate)) {
my $Type = $Rule->{$Field};
# appointment fields are named differently
my $AppointmentField = $Field;
$AppointmentField =~ s/Date$/Time/;
# check if we are dealing with a registered type
if ( $TicketAppointmentTypes{$Type} && $TicketAppointmentTypes{$Type}->{Module} ) {
my $GenericModule = $TicketAppointmentTypes{$Type}->{Module};
# set the time value via the module method
if ( $MainObject->Require($GenericModule) ) {
# loop protection: prevent ticket event module from running
$TicketObject->{'_TicketAppointments::AlreadyProcessed'}->{ $Param{TicketID} }++;
my $Success = $GenericModule->new( %{$Self} )->SetTime(
Type => $Type,
Value => $AppointmentData{$AppointmentField},
TicketID => $Param{TicketID},
);
if ( !$Success ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Error setting $Type for ticket $Param{TicketID}!",
);
}
}
}
}
if ( $Kernel::OM->Get('Kernel::Config')->Get('Debug') ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'debug',
Message => "Updated ticket $Param{TicketID} from appointment $Param{AppointmentID}.",
);
}
return;
}
=head2 TicketAppointmentTicketID()
get ticket id of a ticket appointment.
my $TicketID = $CalendarObject->TicketAppointmentTicketID(
AppointmentID => 1,
);
returns appointment ID if successful.
=cut
sub TicketAppointmentTicketID {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{AppointmentID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need AppointmentID!',
);
return;
}
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# db query
return if !$DBObject->Prepare(
SQL => '
SELECT ticket_id
FROM calendar_appointment_ticket
WHERE appointment_id = ?
',
Bind => [ \$Param{AppointmentID}, ],
Limit => 1,
);
my $TicketID;
while ( my @Row = $DBObject->FetchrowArray() ) {
$TicketID = $Row[0];
}
return $TicketID;
}
=head2 TicketAppointmentRuleIDsGet()
get used ticket appointment rules for specific calendar.
my @RuleIDs = $CalendarObject->TicketAppointmentRuleIDsGet(
CalendarID => 1,
TicketID => 1, # (optional) Return rules used only for specific ticket
);
returns array of rule IDs if found.
=cut
sub TicketAppointmentRuleIDsGet {
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;
}
}
my $SQL = '
SELECT rule_id
FROM calendar_appointment_ticket
WHERE calendar_id = ?
';
my @Bind;
push @Bind, \$Param{CalendarID};
# specific ticket query condition
if ( $Param{TicketID} ) {
$SQL .= '
AND ticket_id = ?
';
push @Bind, \$Param{TicketID};
}
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# db query
return if !$DBObject->Prepare(
SQL => $SQL,
Bind => \@Bind,
);
my %RuleIDs;
while ( my @Row = $DBObject->FetchrowArray() ) {
$RuleIDs{ $Row[0] } = 1;
}
# return unique rule ids
return keys %RuleIDs;
}
=head2 TicketAppointmentRuleGet()
get ticket appointment rule.
my $Rule = $CalendarObject->TicketAppointmentRuleGet(
CalendarID => 1,
RuleID => '9bb20ea035e7a9930652a9d82d00c725',
);
returns rule hash:
=cut
sub TicketAppointmentRuleGet {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(CalendarID RuleID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my %Calendar = $Self->CalendarGet(
CalendarID => $Param{CalendarID},
);
return if !$Calendar{TicketAppointments};
my $Result;
RULE:
for my $Rule ( @{ $Calendar{TicketAppointments} || [] } ) {
if ( $Rule->{RuleID} eq $Param{RuleID} ) {
$Result = $Rule;
last RULE;
}
}
return if !$Result;
return $Result;
}
=head2 TicketAppointmentTypesGet()
get defined ticket appointment types from config.
my %TicketAppointmentTypes = $CalendarObject->TicketAppointmentTypesGet();
returns hash of appointment types:
%TicketAppointmentTypes = ();
=cut
sub TicketAppointmentTypesGet {
my ( $Self, %Param ) = @_;
# get ticket appointment types
my $TicketAppointmentConfig = $Kernel::OM->Get('Kernel::Config')->Get('AppointmentCalendar::TicketAppointmentType')
// {};
return if !$TicketAppointmentConfig;
my %TicketAppointmentTypes;
my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField');
TYPE:
for my $TypeKey ( sort keys %{$TicketAppointmentConfig} ) {
next TYPE if !$TicketAppointmentConfig->{$TypeKey}->{Key};
if ( $TypeKey =~ /DynamicField$/ ) {
# get list of all valid date and date/time dynamic fields
my $DynamicFieldList = $DynamicFieldObject->DynamicFieldListGet(
ObjectType => 'Ticket',
);
DYNAMICFIELD:
for my $DynamicField ( @{$DynamicFieldList} ) {
next DYNAMICFIELD if $DynamicField->{FieldType} ne 'Date' && $DynamicField->{FieldType} ne 'DateTime';
my $Key = sprintf( $TicketAppointmentConfig->{$TypeKey}->{Key}, $DynamicField->{Name} );
$TicketAppointmentTypes{$Key} = $TicketAppointmentConfig->{$TypeKey};
}
next TYPE;
}
$TicketAppointmentTypes{ $TicketAppointmentConfig->{$TypeKey}->{Key} } =
$TicketAppointmentConfig->{$TypeKey};
}
return %TicketAppointmentTypes;
}
=head2 TicketAppointmentDelete()
delete ticket appointment(s).
my $Success = $CalendarObject->TicketAppointmentDelete(
CalendarID => 1, # (required) CalendarID
RuleID => '9bb20ea035e7a9930652a9d82d00c725', # (required) RuleID
# or
TicketID => 1, # (required) Ticket ID
AppointmentID => 1, # (optional) Appointment ID is known
);
returns 1 if successful.
=cut
sub TicketAppointmentDelete {
my ( $Self, %Param ) = @_;
if ( ( !$Param{CalendarID} || !$Param{RuleID} ) && !$Param{TicketID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need CalendarID and RuleID, or TicketID!',
);
return;
}
my @AppointmentIDs;
push @AppointmentIDs, $Param{AppointmentID} if $Param{AppointmentID};
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# Appointment ID is unknown.
if ( !@AppointmentIDs ) {
my $SQL = '
SELECT appointment_id
FROM calendar_appointment_ticket
WHERE 1=1
';
my @Bind;
if ( $Param{CalendarID} && $Param{RuleID} ) {
$SQL .= '
AND calendar_id = ? AND rule_id = ?
';
push @Bind, \$Param{CalendarID}, \$Param{RuleID};
}
if ( $Param{TicketID} ) {
$SQL .= '
AND ticket_id = ?
';
push @Bind, \$Param{TicketID};
}
# db query
return if !$DBObject->Prepare(
SQL => $SQL,
Bind => \@Bind,
);
while ( my @Row = $DBObject->FetchrowArray() ) {
push @AppointmentIDs, $Row[0];
}
}
# Remove the relation(s) from database.
my $SQL = '
DELETE FROM calendar_appointment_ticket
WHERE 1=1
';
my @Bind;
if ( $Param{CalendarID} && $Param{RuleID} ) {
$SQL .= '
AND calendar_id = ? AND rule_id = ?
';
push @Bind, \$Param{CalendarID}, \$Param{RuleID};
}
if ( $Param{TicketID} ) {
$SQL .= '
AND ticket_id = ?
';
push @Bind, \$Param{TicketID};
}
return if !$DBObject->Do(
SQL => $SQL,
Bind => \@Bind,
);
# get appointment object
my $AppointmentObject = $Kernel::OM->Get('Kernel::System::Calendar::Appointment');
# cleanup ticket appointments
APPOINTMENT_ID:
for my $AppointmentID (@AppointmentIDs) {
# check if appointment exists
next APPOINTMENT_ID if !$AppointmentObject->AppointmentGet(
AppointmentID => $AppointmentID,
);
# delete the appointment
return if !$AppointmentObject->AppointmentDelete(
AppointmentID => $AppointmentID,
UserID => 1,
);
}
return 1;
}
=head2 GetAccessToken()
get access token for the calendar.
my $Token = $CalendarObject->GetAccessToken(
CalendarID => 1, # (required) CalendarID
UserLogin => 'agent-1', # (required) User login
);
Returns:
$Token = 'rw';
=cut
sub GetAccessToken {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(CalendarID UserLogin)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# create db object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# db query
return if !$DBObject->Prepare(
SQL => 'SELECT salt_string FROM calendar WHERE id = ?',
Bind => [ \$Param{CalendarID} ],
Limit => 1,
);
# fetch the result
my $SaltString;
while ( my @Row = $DBObject->FetchrowArray() ) {
$SaltString = $Row[0];
}
return if !$SaltString;
# Encode user login to UTF8 representation, since MD5 is defined only for strings of bytes.
# If login contains a Unicode character, MD5 might fail otherwise. Please see bug#12593 for
# more information.
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Param{UserLogin} );
# calculate md5 sum
my $String = "$Param{UserLogin}-$SaltString";
my $MD5 = Digest::MD5->new()->add($String)->hexdigest();
return $MD5;
}
=head2 GetTextColor()
Returns best text color for supplied background, based on luminosity difference algorithm.
my $BestTextColor = $CalendarObject->GetTextColor(
Background => '#FFF', # (required) must be in valid hexadecimal RGB notation
);
Returns:
$BestTextColor = '#000';
=cut
sub GetTextColor {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(Background)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# check color
if ( !( $Param{Background} =~ /#[A-F0-9]{3,6}/i ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Background must be in hexadecimal RGB notation, eg. #FFFFFF.',
);
return;
}
# check if value is cached
my $Data = $Kernel::OM->Get('Kernel::System::Cache')->Get(
Type => $Self->{CacheType} . 'GetTextColor',
Key => $Param{Background},
);
return $Data if $Data;
# get RGB values
my @BackgroundColor;
my $RGBHex = substr( $Param{Background}, 1 );
# six character hexadecimal string (eg. #FFFFFF)
if ( length $RGBHex == 6 ) {
$BackgroundColor[0] = hex substr( $RGBHex, 0, 2 );
$BackgroundColor[1] = hex substr( $RGBHex, 2, 2 );
$BackgroundColor[2] = hex substr( $RGBHex, 4, 2 );
}
# three character hexadecimal string (eg. #FFF)
elsif ( length $RGBHex == 3 ) {
$BackgroundColor[0] = hex( substr( $RGBHex, 0, 1 ) . substr( $RGBHex, 0, 1 ) );
$BackgroundColor[1] = hex( substr( $RGBHex, 1, 1 ) . substr( $RGBHex, 1, 1 ) );
$BackgroundColor[2] = hex( substr( $RGBHex, 2, 1 ) . substr( $RGBHex, 1, 1 ) );
}
# invalid hexadecimal string
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Background must be in valid 3 or 6 character hexadecimal RGB notation, eg. #FFF or #FFFFFF.',
);
return;
}
# predefined text colors
my %TextColors = (
White => [ '255', '255', '255' ],
Gray => [ '128', '128', '128' ],
Black => [ '0', '0', '0' ],
);
# calculate background luminosity
my $BackgroundLum =
0.2126 * ( $BackgroundColor[0] / 255**2.2 ) +
0.7152 * ( $BackgroundColor[1] / 255**2.2 ) +
0.0722 * ( $BackgroundColor[2] / 255**2.2 );
# calculate luminosity difference
my %LumDiff;
for my $TextColor ( sort keys %TextColors ) {
my $TextLum =
0.2126 * ( $TextColors{$TextColor}->[0] / 255**2.2 ) +
0.7152 * ( $TextColors{$TextColor}->[1] / 255**2.2 ) +
0.0722 * ( $TextColors{$TextColor}->[2] / 255**2.2 );
if ( $BackgroundLum > $TextLum ) {
$LumDiff{$TextColor} = ( $BackgroundLum + 0.05 ) / ( $TextLum + 0.05 );
}
else {
$LumDiff{$TextColor} = ( $TextLum + 0.05 ) / ( $BackgroundLum + 0.05 );
}
}
# get maximum luminosity difference
my ($MaxLumDiff) = sort { $b <=> $a } values %LumDiff;
return if !$MaxLumDiff;
# identify best suited color
my ($BestTextColor) = grep { $LumDiff{$_} eq $MaxLumDiff } keys %LumDiff;
return if !$BestTextColor;
# convert to hex string
my $TextColor = sprintf(
'#%X%X%X',
$TextColors{$BestTextColor}->[0],
$TextColors{$BestTextColor}->[1],
$TextColors{$BestTextColor}->[2],
);
# cache
$Kernel::OM->Get('Kernel::System::Cache')->Set(
Type => $Self->{CacheType} . 'GetTextColor',
Key => $Param{Background},
Value => $TextColor,
TTL => $Self->{CacheTTL},
);
return $TextColor;
}
=begin Internal:
=head2 _TicketAppointmentGet()
get ticket appointment id if exists.
my $AppointmentID = $CalendarObject->_TicketAppointmentGet(
CalendarID => 1,
TicketID => 1,
RuleID => '9bb20ea035e7a9930652a9d82d00c725',
);
returns appointment ID if successful.
=cut
sub _TicketAppointmentGet {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(CalendarID TicketID RuleID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# db query
return if !$DBObject->Prepare(
SQL => '
SELECT appointment_id
FROM calendar_appointment_ticket
WHERE calendar_id = ? AND ticket_id = ? AND rule_id = ?
',
Bind => [ \$Param{CalendarID}, \$Param{TicketID}, \$Param{RuleID}, ],
Limit => 1,
);
my $AppointmentID;
while ( my @Row = $DBObject->FetchrowArray() ) {
$AppointmentID = $Row[0];
}
return $AppointmentID;
}
=head2 _TicketAppointmentList()
Get list of ticket appointments based on a rule.
my %Appointments = $CalendarObject->_TicketAppointmentList(
CalendarID => 1,
RuleID => '9bb20ea035e7a9930652a9d82d00c725',
Key => 'TicketID', # (optional) Return result will be based on this key.
Default: TicketID, Possible: TicketID|AppointmentID
);
Returns list of ticket appointments, where key will be either TicketID (default) or AppointmentID:
%Appointments = (
1 => 1,
2 => 2,
...
);
=cut
sub _TicketAppointmentList {
my ( $Self, %Param ) = @_;
for my $Needed (qw(CalendarID RuleID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
$Param{Key} ||= 'TicketID';
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
return if !$DBObject->Prepare(
SQL => '
SELECT appointment_id, ticket_id
FROM calendar_appointment_ticket
WHERE calendar_id = ? AND rule_id = ?
',
Bind => [ \$Param{CalendarID}, \$Param{RuleID}, ],
);
my %Result;
while ( my @Row = $DBObject->FetchrowArray() ) {
$Result{ $Row[1] } = $Row[0];
}
if ( $Param{Key} eq 'AppointmentID' ) {
%Result = reverse %Result;
}
return %Result;
}
=head2 _TicketAppointmentCreate()
create ticket appointment.
my $Success = $CalendarObject->_TicketAppointmentCreate(
CalendarID => 1,
TicketID => 1,
RuleID => '9bb20ea035e7a9930652a9d82d00c725',
Title => '[Ticket#20160823810000010] Some Ticket Title',
StartTime => '2016-08-23 00:00:00',
EndTime => '2016-08-24 00:00:00',
);
returns 1 if successful.
=cut
sub _TicketAppointmentCreate {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(CalendarID TicketID RuleID Title StartTime EndTime)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# create appointment
my $AppointmentID = $Kernel::OM->Get('Kernel::System::Calendar::Appointment')->AppointmentCreate(
CalendarID => $Param{CalendarID},
Title => $Param{Title},
StartTime => $Param{StartTime},
EndTime => $Param{EndTime},
TicketAppointmentRuleID => $Param{RuleID},
UserID => 1,
);
return if !$AppointmentID;
# save the relation in database
return $Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => '
INSERT INTO calendar_appointment_ticket
(calendar_id, ticket_id, appointment_id, rule_id)
VALUES (?, ?, ?, ?)
',
Bind => [ \$Param{CalendarID}, \$Param{TicketID}, \$AppointmentID, \$Param{RuleID}, ],
);
}
=head2 _TicketAppointmentUpdate()
update ticket appointment.
my $Success = $CalendarObject->_TicketAppointmentUpdate(
AppointmentID => 1,
TicketID => 1,
RuleID => '9bb20ea035e7a9930652a9d82d00c725',
Title => '[Ticket#20160823810000010] Some Ticket Title',
StartTime => '2016-08-23 00:00:00',
EndTime => '2016-08-24 00:00:00',
);
returns 1 if successful.
=cut
sub _TicketAppointmentUpdate {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(AppointmentID TicketID RuleID Title StartTime EndTime)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# get appointment object
my $AppointmentObject = $Kernel::OM->Get('Kernel::System::Calendar::Appointment');
# get current ticket appointment data
my %Appointment = $AppointmentObject->AppointmentGet(
AppointmentID => $Param{AppointmentID},
);
# ticket appointment does not exist
if ( !$Appointment{AppointmentID} ) {
# remove the relation as well
$Self->TicketAppointmentDelete(
%Param,
);
# create new ticket appointment
return $Self->_TicketAppointmentCreate(
%Param,
);
}
# loop protection: prevent appointment event module from running
$Self->{'_TicketAppointments::TicketUpdate'}->{ $Appointment{AppointmentID} }++;
# update ticket appointment
return $AppointmentObject->AppointmentUpdate(
%Appointment,
Title => $Param{Title},
StartTime => $Param{StartTime},
EndTime => $Param{EndTime},
TicketAppointmentRuleID => $Param{RuleID},
UserID => 1,
);
}
1;
=end Internal:
=head1 TERMS AND CONDITIONS
This software is part of the OTRS project (L<https://otrs.org/>).
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<https://www.gnu.org/licenses/gpl-3.0.txt>.
=cut