# --
# Copyright (C) 2001-2020 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::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).
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