This commit is contained in:
2024-10-14 00:08:40 +02:00
parent dbfba56f66
commit 1462d52e13
4572 changed files with 2658864 additions and 0 deletions

View File

@@ -0,0 +1,169 @@
# --
# 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::Console::Command::Maint::Ticket::ArchiveCleanup;
use strict;
use warnings;
use Time::HiRes();
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::DB',
'Kernel::System::Ticket',
'Kernel::System::Ticket::Article',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description('Delete ticket/article seen flags and ticket watcher entries for archived tickets.');
$Self->AddOption(
Name => 'micro-sleep',
Description => "Specify microseconds to sleep after every ticket to reduce system load (e.g. 1000).",
Required => 0,
HasValue => 1,
ValueRegex => qr/^\d+$/smx,
);
return;
}
sub PreRun {
my ( $Self, %Param ) = @_;
if ( !$Kernel::OM->Get('Kernel::Config')->Get('Ticket::ArchiveSystem') ) {
die "Ticket::ArchiveSystem is not enabled!\n";
}
return;
}
sub Run {
my ( $Self, %Param ) = @_;
$Self->Print("<yellow>Cleaning up ticket archive...</yellow>\n");
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
my $MicroSleep = $Self->GetOption('micro-sleep');
if ( $ConfigObject->Get('Ticket::ArchiveSystem::RemoveSeenFlags') ) {
$Self->Print("<yellow>Checking for archived tickets with seen flags...</yellow>\n");
# Find all archived tickets which have ticket seen flags set
return if !$DBObject->Prepare(
SQL => "
SELECT DISTINCT(ticket.id)
FROM ticket
INNER JOIN ticket_flag ON ticket.id = ticket_flag.ticket_id
WHERE ticket.archive_flag = 1
AND ticket_flag.ticket_key = 'Seen'",
Limit => 1_000_000,
);
my @TicketIDs;
while ( my @Row = $DBObject->FetchrowArray() ) {
push @TicketIDs, $Row[0];
}
my $Count = 0;
for my $TicketID (@TicketIDs) {
$TicketObject->TicketFlagDelete(
TicketID => $TicketID,
Key => 'Seen',
AllUsers => 1,
);
$Count++;
$Self->Print(" Removing seen flags of ticket $TicketID\n");
Time::HiRes::usleep($MicroSleep) if $MicroSleep;
}
$Self->Print("<green>Done</green> (changed <yellow>$Count</yellow> tickets).\n");
$Self->Print("<yellow>Checking for archived articles with seen flags...</yellow>\n");
# Find all articles of archived tickets which have ticket seen flags set
return if !$DBObject->Prepare(
SQL => "
SELECT DISTINCT(article.id)
FROM article
INNER JOIN ticket ON ticket.id = article.ticket_id
INNER JOIN article_flag ON article.id = article_flag.article_id
WHERE ticket.archive_flag = 1
AND article_flag.article_key = 'Seen'",
Limit => 1_000_000,
);
my @ArticleIDs;
while ( my @Row = $DBObject->FetchrowArray() ) {
push @ArticleIDs, $Row[0];
}
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
$Count = 0;
for my $ArticleID (@ArticleIDs) {
$ArticleObject->ArticleFlagDelete(
ArticleID => $ArticleID,
Key => 'Seen',
AllUsers => 1,
);
$Count++;
$Self->Print(" Removing seen flags of article $ArticleID\n");
Time::HiRes::usleep($MicroSleep) if $MicroSleep;
}
$Self->Print("<green>Done</green> (changed <yellow>$Count</yellow> articles).\n");
}
if (
$ConfigObject->Get('Ticket::ArchiveSystem::RemoveTicketWatchers')
&& $ConfigObject->Get('Ticket::Watcher')
)
{
$Self->Print("<yellow>Checking for archived tickets with ticket watcher entries...</yellow>\n");
# Find all archived tickets which have ticket seen flags set
return if !$DBObject->Prepare(
SQL => "
SELECT DISTINCT(ticket.id)
FROM ticket
INNER JOIN ticket_watcher ON ticket.id = ticket_watcher.ticket_id
WHERE ticket.archive_flag = 1",
Limit => 1_000_000,
);
my $Count = 0;
ROW:
while ( my @Row = $DBObject->FetchrowArray() ) {
$TicketObject->TicketWatchUnsubscribe(
TicketID => $Row[0],
AllUsers => 1,
UserID => 1,
);
$Self->Print(" Removing ticket watcher entries of ticket $Count\n");
Time::HiRes::usleep($MicroSleep) if $MicroSleep;
}
$Self->Print("<green>Done</green> (changed <yellow>$Count</yellow> tickets).\n");
}
$Self->Print("<green>Done.</green>\n");
return $Self->ExitCodeOk();
}
1;

View File

@@ -0,0 +1,134 @@
# --
# 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::Console::Command::Maint::Ticket::Delete;
use strict;
use warnings;
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::System::Ticket',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description('Delete one or more tickets.');
$Self->AddOption(
Name => 'ticket-number',
Description => "Specify one or more ticket numbers of tickets to be deleted.",
Required => 0,
HasValue => 1,
ValueRegex => qr/.*/smx,
Multiple => 1,
);
$Self->AddOption(
Name => 'ticket-id',
Description => "Specify one or more ticket ids of tickets to be deleted.",
Required => 0,
HasValue => 1,
ValueRegex => qr/\d+/smx,
Multiple => 1,
);
return;
}
sub PreRun {
my ( $Self, %Param ) = @_;
my @TicketIDs = @{ $Self->GetOption('ticket-id') // [] };
my @TicketNumbers = @{ $Self->GetOption('ticket-number') // [] };
if ( !@TicketIDs && !@TicketNumbers ) {
die "Please provide option --ticket-id or --ticket-number.\n";
}
return;
}
sub Run {
my ( $Self, %Param ) = @_;
$Self->Print("<yellow>Deleting tickets...</yellow>\n");
my @TicketIDs = @{ $Self->GetOption('ticket-id') // [] };
my @TicketNumbers = @{ $Self->GetOption('ticket-number') // [] };
my @DeleteTicketIDs;
TICKETNUMBER:
for my $TicketNumber (@TicketNumbers) {
# lookup ticket id
my $TicketID = $Kernel::OM->Get('Kernel::System::Ticket')->TicketIDLookup(
TicketNumber => $TicketNumber,
UserID => 1,
);
# error handling
if ( !$TicketID ) {
$Self->PrintError("Unable to find ticket number $TicketNumber.\n");
next TICKETNUMBER;
}
push @DeleteTicketIDs, $TicketID;
}
TICKETID:
for my $TicketID (@TicketIDs) {
# lookup ticket number
my $TicketNumber = $Kernel::OM->Get('Kernel::System::Ticket')->TicketNumberLookup(
TicketID => $TicketID,
UserID => 1,
);
# error handling
if ( !$TicketNumber ) {
$Self->PrintError("Unable to find ticket id $TicketID.\n");
next TICKETID;
}
push @DeleteTicketIDs, $TicketID;
}
my $DeletedTicketCount = 0;
TICKETID:
for my $TicketID (@DeleteTicketIDs) {
# delete the ticket
my $True = $Kernel::OM->Get('Kernel::System::Ticket')->TicketDelete(
TicketID => $TicketID,
UserID => 1,
);
# error handling
if ( !$True ) {
$Self->PrintError("Unable to delete ticket with id $TicketID\n");
next TICKETID;
}
$Self->Print(" $TicketID\n");
# increase the deleted ticket count
$DeletedTicketCount++;
}
if ( !$DeletedTicketCount ) {
return $Self->ExitCodeError();
}
$Self->Print("<green>$DeletedTicketCount tickets have been deleted.</green>\n");
return $Self->ExitCodeOk();
}
1;

View File

@@ -0,0 +1,136 @@
# --
# 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::Console::Command::Maint::Ticket::Dump;
use strict;
use warnings;
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::Output::HTML::Layout',
'Kernel::System::CommunicationChannel',
'Kernel::System::Ticket',
'Kernel::System::Ticket::Article',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description('Print a ticket and its articles to the console.');
$Self->AddOption(
Name => 'article-limit',
Description => "Maximum number of articles to print.",
Required => 0,
HasValue => 1,
ValueRegex => qr/\d+/smx,
);
$Self->AddArgument(
Name => 'ticket-id',
Description => "ID of the ticket to be printed.",
Required => 1,
ValueRegex => qr/\d+/smx,
);
return;
}
sub Run {
my ( $Self, %Param ) = @_;
my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet(
TicketID => $Self->GetArgument('ticket-id'),
DynamicFields => 0,
);
if ( !%Ticket ) {
$Self->PrintError("Could not find ticket.");
return $Self->ExitCodeError();
}
$Self->Print( "<green>" . ( '=' x 69 ) . "</green>\n" );
KEY:
for my $Key (qw(TicketNumber TicketID Title Created Queue State Priority Lock CustomerID CustomerUserID)) {
next KEY if !$Ticket{$Key};
$Self->Print( sprintf( "<yellow>%-20s</yellow> %s\n", "$Key:", $Ticket{$Key} ) );
}
$Self->Print( "<green>" . ( '-' x 69 ) . "</green>\n" );
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
# get article index
my @MetaArticles = $ArticleObject->ArticleList(
TicketID => $Self->GetArgument('ticket-id'),
);
$Kernel::OM->ObjectParamAdd(
'Kernel::Output::HTML::Layout' => {
UserID => 1,
},
);
## nofilter(TidyAll::Plugin::OTRS::Perl::LayoutObject)
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
my $Counter = 1;
my $ArticleLimit = $Self->GetOption('article-limit');
META_ARTICLE:
for my $MetaArticle (@MetaArticles) {
last META_ARTICLE if defined $ArticleLimit && $ArticleLimit < $Counter;
# get article data
my %Article = $ArticleObject->BackendForArticle( %{$MetaArticle} )->ArticleGet(
%{$MetaArticle},
DynamicFields => 0,
);
next META_ARTICLE if !%Article;
my %CommunicationChannel = $Kernel::OM->Get('Kernel::System::CommunicationChannel')->ChannelGet(
ChannelID => $Article{CommunicationChannelID},
);
$Article{Channel} = $CommunicationChannel{ChannelName};
KEY:
for my $Key (qw(ArticleID CreateTime SenderType Channel)) {
next KEY if !$Article{$Key};
$Self->Print( sprintf( "<yellow>%-20s</yellow> %s\n", "$Key:", $Article{$Key} ) );
}
my %ArticleFields = $LayoutObject->ArticleFields(%Article);
for my $ArticleFieldKey (
sort { $ArticleFields{$a}->{Prio} <=> $ArticleFields{$b}->{Prio} }
keys %ArticleFields
)
{
my %ArticleField = %{ $ArticleFields{$ArticleFieldKey} // {} };
$Self->Print( sprintf( "<yellow>%-20s</yellow> %s\n", "$ArticleField{Label}:", $ArticleField{Value} ) );
}
$Self->Print( "<green>" . ( '-' x 69 ) . "</green>\n" );
my $ArticlePreview = $LayoutObject->ArticlePreview(
%Article,
ResultType => 'plain',
);
$Self->Print("$ArticlePreview\n");
$Self->Print( "<green>" . ( '-' x 69 ) . "</green>\n" );
}
continue {
$Counter++;
}
return $Self->ExitCodeOk();
}
1;

View File

@@ -0,0 +1,217 @@
# --
# 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::Console::Command::Maint::Ticket::EscalationCheck;
use strict;
use warnings;
use List::Util qw(first);
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::DateTime',
'Kernel::System::Ticket',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description('Trigger ticket escalation events and notification events for escalation.');
return;
}
# =item Run()
#
# looks for the tickets which will escalate within the next five days
# then perform a search over the values on the TicketGet result
# for checking if any of the escalations values are present, then base
# on that the notification events for notifications and normal escalation
# events are trigger
#
# NotificationEvents:
# - NotificationEscalation
# - NotificationEscalationNotifyBefore
#
# Escalation events:
# - EscalationResponseTimeStart
# - EscalationUpdateTimeStart
# - EscalationSolutionTimeStart
# - EscalationResponseTimeNotifyBefore
# - EscalationUpdateTimeNotifyBefore
# - EscalationSolutionTimeNotifyBefore
#
#
# NotificationEvents are alway triggered, and Escalation events just
# base on the 'OTRSEscalationEvents::DecayTime'.
#
# =cut
sub Run {
my ( $Self, %Param ) = @_;
$Self->Print("<yellow>Processing ticket escalation events ...</yellow>\n");
# get needed objects
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
# the decay time is configured in minutes
my $DecayTimeInSeconds = $Kernel::OM->Get('Kernel::Config')->Get('OTRSEscalationEvents::DecayTime') || 0;
$DecayTimeInSeconds *= 60;
# check if it's a escalation or escalation notification
# check escalation times
my %TicketAttr2Event = (
FirstResponseTimeEscalation => 'EscalationResponseTimeStart',
UpdateTimeEscalation => 'EscalationUpdateTimeStart',
SolutionTimeEscalation => 'EscalationSolutionTimeStart',
FirstResponseTimeNotification => 'EscalationResponseTimeNotifyBefore',
UpdateTimeNotification => 'EscalationUpdateTimeNotifyBefore',
SolutionTimeNotification => 'EscalationSolutionTimeNotifyBefore',
);
# Find all tickets which will escalate within the next five days.
my @Tickets = $TicketObject->TicketSearch(
Result => 'ARRAY',
Limit => 1000,
TicketEscalationTimeOlderMinutes => -( 5 * 24 * 60 ),
Permission => 'rw',
UserID => 1,
);
TICKET:
for my $TicketID (@Tickets) {
# get ticket data
my %Ticket = $TicketObject->TicketGet(
TicketID => $TicketID,
DynamicFields => 0,
);
# get used calendar
my $Calendar = $TicketObject->TicketCalendarGet(
%Ticket,
);
# check if it is during business hours, then send escalation info
my $BusinessStartDTObject = $Kernel::OM->Create('Kernel::System::DateTime');
$BusinessStartDTObject->Subtract( Seconds => 10 * 60 );
my $BusinessStopDTObject = $Kernel::OM->Create('Kernel::System::DateTime');
my $CountedTime = $BusinessStartDTObject->Delta(
DateTimeObject => $BusinessStopDTObject,
ForWorkingTime => 1,
Calendar => $Calendar,
);
# don't trigger events if not counted time
if ( !$CountedTime || !$CountedTime->{AbsoluteSeconds} ) {
next TICKET;
}
# needed for deciding whether events should be triggered
my @HistoryLines = $TicketObject->HistoryGet(
TicketID => $TicketID,
UserID => 1,
);
# check if it's a escalation of escalation notification
# check escalation times
my $EscalationType = 0;
my @Events;
TYPE:
for my $Type (
qw(FirstResponseTimeEscalation UpdateTimeEscalation SolutionTimeEscalation
FirstResponseTimeNotification UpdateTimeNotification SolutionTimeNotification)
)
{
next TYPE if !$Ticket{$Type};
my @ReversedHistoryLines = reverse @HistoryLines;
# get the last time this event was triggered
# search in reverse order, as @HistoryLines sorted ascendingly by CreateTime
if ($DecayTimeInSeconds) {
my $PrevEventLine = first { $_->{HistoryType} eq $TicketAttr2Event{$Type} }
@ReversedHistoryLines;
if ( $PrevEventLine && $PrevEventLine->{CreateTime} ) {
my $PrevEventTime = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
String => $PrevEventLine->{CreateTime},
},
)->ToEpoch();
my $CurSysTime = $Kernel::OM->Create('Kernel::System::DateTime')->ToEpoch();
my $TimeSincePrevEvent = $CurSysTime - $PrevEventTime;
next TYPE if $TimeSincePrevEvent <= $DecayTimeInSeconds;
}
}
# emit the event
push @Events, $TicketAttr2Event{$Type};
if ( !$EscalationType ) {
if ( $Type =~ /TimeEscalation$/ ) {
push @Events, 'NotificationEscalation';
$EscalationType = 1;
}
elsif ( $Type =~ /TimeNotification$/ ) {
push @Events, 'NotificationEscalationNotifyBefore';
$EscalationType = 1;
}
}
}
EVENT:
for my $Event (@Events) {
# trigger the event
$TicketObject->EventHandler(
Event => $Event,
UserID => 1,
Data => {
TicketID => $TicketID,
CustomerMessageParams => {
TicketNumber => $Ticket{TicketNumber},
},
},
UserID => 1,
);
$Self->Print(
"Ticket <yellow>$TicketID</yellow>: event <yellow>$Event</yellow>, processed.\n"
);
if ( $Event eq 'NotificationEscalation' || $Event eq 'NotificationEscalationNotifyBefore' ) {
next EVENT;
}
# log the triggered event in the history
$TicketObject->HistoryAdd(
TicketID => $TicketID,
HistoryType => $Event,
Name => "%%$Event%%triggered",
CreateUserID => 1,
);
}
}
$Self->Print("<green>Done.</green>\n");
return $Self->ExitCodeOk();
}
1;

View File

@@ -0,0 +1,81 @@
# --
# 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::Console::Command::Maint::Ticket::EscalationIndexRebuild;
use strict;
use warnings;
use Time::HiRes();
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::Ticket',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description('Completely rebuild the ticket escalation index.');
$Self->AddOption(
Name => 'micro-sleep',
Description => "Specify microseconds to sleep after every ticket to reduce system load (e.g. 1000).",
Required => 0,
HasValue => 1,
ValueRegex => qr/^\d+$/smx,
);
return;
}
sub Run {
my ( $Self, %Param ) = @_;
$Self->Print("<yellow>Rebuilding ticket escalation index...</yellow>\n");
# disable ticket events
$Kernel::OM->Get('Kernel::Config')->{'Ticket::EventModulePost'} = {};
# get all tickets
my @TicketIDs = $Kernel::OM->Get('Kernel::System::Ticket')->TicketSearch(
Result => 'ARRAY',
Limit => 100_000_000,
UserID => 1,
Permission => 'ro',
);
my $Count = 0;
my $MicroSleep = $Self->GetOption('micro-sleep');
TICKETID:
for my $TicketID (@TicketIDs) {
$Count++;
$Kernel::OM->Get('Kernel::System::Ticket')->TicketEscalationIndexBuild(
TicketID => $TicketID,
UserID => 1,
);
if ( $Count % 2000 == 0 ) {
my $Percent = int( $Count / ( $#TicketIDs / 100 ) );
$Self->Print(
"<yellow>$Count</yellow> of <yellow>$#TicketIDs</yellow> processed (<yellow>$Percent %</yellow> done).\n"
);
}
Time::HiRes::usleep($MicroSleep) if $MicroSleep;
}
$Self->Print("<green>Done.</green>\n");
return $Self->ExitCodeOk();
}
1;

View File

@@ -0,0 +1,108 @@
# --
# 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::Console::Command::Maint::Ticket::FulltextIndex;
use strict;
use warnings;
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::Ticket::Article',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description(
'Flag articles to automatically rebuild the article search index or displays the index status. Please use --status or --rebuild option, not both!'
);
$Self->AddOption(
Name => 'status',
Description => "Display the current status of the index.",
Required => 0,
HasValue => 0,
);
$Self->AddOption(
Name => 'rebuild',
Description => "Flag articles to automatically rebuild the article search index.",
Required => 0,
HasValue => 0,
);
return;
}
sub PreRun {
my ( $Self, %Param ) = @_;
if ( !$Self->GetOption('status') && !$Self->GetOption('rebuild') ) {
$Self->Print( $Self->GetUsageHelp() );
die "Either --status or --rebuild must be given!\n";
}
if ( $Self->GetOption('status') && $Self->GetOption('rebuild') ) {
$Self->Print( $Self->GetUsageHelp() );
die "Either --status or --rebuild must be given, not both!\n";
}
return;
}
sub Run {
my ( $Self, %Param ) = @_;
if ( $Self->GetOption('status') ) {
$Self->Status();
}
elsif ( $Self->GetOption('rebuild') ) {
$Self->Rebuild();
}
return $Self->ExitCodeOk();
}
sub Status {
my ( $Self, %Param ) = @_;
my %Status = $Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleSearchIndexStatus();
my $Percentage = $Status{ArticlesIndexed} / $Status{ArticlesTotal} * 100;
my $Color = $Percentage == 100 ? 'green' : 'yellow';
my $Output = sprintf(
"Indexed Articles: <$Color>%.1f%%</$Color> (<$Color>%d/%d</$Color>)",
$Percentage,
$Status{ArticlesIndexed},
$Status{ArticlesTotal}
);
$Self->Print("$Output\n");
return 1;
}
sub Rebuild {
my ( $Self, %Param ) = @_;
$Self->Print("<yellow>Marking all articles for reindexing...</yellow>\n");
$Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleSearchIndexRebuildFlagSet(
All => 1,
Value => 1,
);
$Self->Print("<green>Done.</green>\n");
return 1;
}
1;

View File

@@ -0,0 +1,261 @@
# --
# 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.
# --
## nofilter(TidyAll::Plugin::OTRS::Perl::NoExitInConsoleCommands)
package Kernel::System::Console::Command::Maint::Ticket::FulltextIndexRebuildWorker;
use strict;
use warnings;
use parent qw(Kernel::System::Console::BaseCommand);
use POSIX ":sys_wait_h";
use Time::HiRes qw(sleep);
use Kernel::System::VariableCheck qw(:all);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::DB',
'Kernel::System::Log',
'Kernel::System::PID',
'Kernel::System::Ticket',
'Kernel::System::Ticket::Article',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description('Rebuild the article search index for needed articles.');
$Self->AddOption(
Name => 'children',
Description => "Specify the number of child processes to be used for indexing (default: 4, maximum: 20).",
Required => 0,
HasValue => 1,
ValueRegex => qr/^\d+$/smx,
);
$Self->AddOption(
Name => 'limit',
Description => "Maximum number of ArticleIDs to process (default: 20000).",
Required => 0,
HasValue => 1,
ValueRegex => qr/^\d+$/smx,
);
$Self->AddOption(
Name => 'force-pid',
Description => "Start even if another process is still registered in the database.",
Required => 0,
HasValue => 0,
);
return;
}
sub PreRun {
my ( $Self, %Param ) = @_;
my $Children = $Self->GetOption('children') // 4;
if ( $Children > 20 ) {
die "The allowed maximum amount of child processes is 20!\n";
}
my $ForcePID = $Self->GetOption('force-pid');
my $PIDObject = $Kernel::OM->Get('Kernel::System::PID');
my %PID = $PIDObject->PIDGet(
Name => 'ArticleSearchIndexRebuild',
);
if ( %PID && !$ForcePID ) {
die "Active indexing process already running! Skipping...\n";
}
my $Success = $PIDObject->PIDCreate(
Name => 'ArticleSearchIndexRebuild',
Force => $Self->GetOption('force-pid'),
);
if ( !$Success ) {
die "Unable to register indexing process! Skipping...\n";
}
return;
}
sub Run {
my ( $Self, %Param ) = @_;
my $Children = $Self->GetOption('children') // 4;
my $Limit = $Self->GetOption('limit') // 20000;
my %ArticleTicketIDs = $Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleSearchIndexRebuildFlagList(
Value => 1,
Limit => $Limit,
);
# perform the indexing if needed
if (%ArticleTicketIDs) {
$Self->ArticleIndexRebuild(
ArticleTicketIDs => \%ArticleTicketIDs,
Children => $Children,
);
}
else {
$Self->Print("<yellow>No indexing needed! Skipping...</yellow>\n");
}
$Self->Print("<green>Done.</green>\n");
return $Self->ExitCodeOk();
}
sub ArticleIndexRebuild {
my ( $Self, %Param ) = @_;
my @ArticleIDs = keys %{ $Param{ArticleTicketIDs} };
$Kernel::OM->Get('Kernel::System::DB')->Disconnect();
# Destroy objects for the child processes.
$Kernel::OM->ObjectsDiscard(
Objects => [
'Kernel::System::DB',
],
ForcePackageReload => 0,
);
# Split ArticleIDs into equal arrays for the child processes.
my @ArticleChunks;
my $Count = 0;
for my $ArticleID (@ArticleIDs) {
push @{ $ArticleChunks[ $Count++ % $Param{Children} ] }, $ArticleID;
}
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
my %ActiveChildPID;
ARTICLEIDCHUNK:
for my $ArticleIDChunk (@ArticleChunks) {
# Create a child process.
my $PID = fork;
# Could not create child.
if ( $PID < 0 ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Unable to fork to a child process for article index rebuild!"
);
last ARTICLEIDCHUNK;
}
# We're in the child process.
if ( !$PID ) {
# Add the chunk of article data to the index.
for my $ArticleID ( @{$ArticleIDChunk} ) {
my $Success = 0;
if (
$ConfigObject->Get('Ticket::ArchiveSystem')
&& !$ConfigObject->Get('Ticket::SearchIndex::IndexArchivedTickets')
&& $TicketObject->TicketArchiveFlagGet( TicketID => $Param{ArticleTicketIDs}->{$ArticleID} )
)
{
$Success = $ArticleObject->ArticleSearchIndexDelete(
ArticleID => $ArticleID,
UserID => 1,
);
}
else {
$Success = $ArticleObject->ArticleSearchIndexBuild(
TicketID => $Param{ArticleTicketIDs}->{$ArticleID},
ArticleID => $ArticleID,
UserID => 1,
);
}
if ( !$Success ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Could not rebuild index for ArticleID '$ArticleID'!"
);
}
else {
$ArticleObject->ArticleSearchIndexRebuildFlagSet(
ArticleIDs => [$ArticleID],
Value => 0,
);
}
}
# Close child process at the end.
exit 0;
}
$ActiveChildPID{$PID} = {
PID => $PID,
};
}
# Check the status of all child processes every 0.1 seconds.
# Wait for all child processes to be finished.
WAIT:
while (1) {
last WAIT if !%ActiveChildPID;
sleep 0.1;
PID:
for my $PID ( sort keys %ActiveChildPID ) {
my $WaitResult = waitpid( $PID, WNOHANG );
if ( $WaitResult == -1 ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Child process exited with errors: $?",
);
delete $ActiveChildPID{$PID};
next PID;
}
delete $ActiveChildPID{$PID} if $WaitResult;
}
}
return 1;
}
sub PostRun {
my ($Self) = @_;
my $Success = $Kernel::OM->Get('Kernel::System::PID')->PIDDelete(
Name => 'ArticleSearchIndexRebuild',
);
if ( !$Success ) {
$Self->PrintError("Unable to unregister indexing process! Skipping...\n");
return $Self->ExitCodeError();
}
return $Success;
}
1;

View File

@@ -0,0 +1,283 @@
# --
# 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::Console::Command::Maint::Ticket::InvalidUserCleanup;
use strict;
use warnings;
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::DB',
'Kernel::System::DateTime',
'Kernel::System::Ticket',
'Kernel::System::User',
'Kernel::System::Ticket::Article',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description(
'Delete ticket/article seen flags and ticket watcher entries of users which have been invalid for more than a month, and unlocks tickets by invalid agents immedately.'
);
$Self->AddOption(
Name => 'micro-sleep',
Description => "Specify microseconds to sleep after every ticket to reduce system load (e.g. 1000).",
Required => 0,
HasValue => 1,
ValueRegex => qr/^\d+$/smx,
);
return;
}
sub Run {
my ( $Self, %Param ) = @_;
$Self->Print("<yellow>Starting invalid user cleanup...</yellow>\n");
my $InvalidID = 2;
# Users must be invalid for at least one month
my $InvalidBeforeDTObject = $Kernel::OM->Create('Kernel::System::DateTime');
$InvalidBeforeDTObject->Subtract( Seconds => 60 * 60 * 24 * 31 );
# get user object
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
my $MicroSleep = $Self->GetOption('micro-sleep');
# First, find all invalid users which are invalid for more than one month
my %AllUsers = $UserObject->UserList( Valid => 0 );
my @CleanupInvalidUsers;
my @CleanupInvalidUsersImmediately;
USERID:
for my $UserID ( sort keys %AllUsers ) {
my %User = $UserObject->GetUserData(
UserID => $UserID,
);
# Only take invalid users
next USERID if ( $User{ValidID} != $InvalidID );
# Only take users which are invalid for more than one month
my $InvalidTimeDTObject = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
String => $User{ChangeTime},
},
);
push @CleanupInvalidUsersImmediately, \%User;
next USERID if ( $InvalidTimeDTObject >= $InvalidBeforeDTObject );
push @CleanupInvalidUsers, \%User;
}
if ( !@CleanupInvalidUsersImmediately ) {
$Self->Print("<green>No cleanup for invalid users is needed.</green>\n");
return $Self->ExitCodeOk();
}
$Self->_CleanupLocks(
InvalidUsers => \@CleanupInvalidUsersImmediately,
MicroSleep => $MicroSleep,
);
if (@CleanupInvalidUsers) {
$Self->_CleanupFlags(
CleanupInvalidUsers => \@CleanupInvalidUsers,
MicroSleep => $MicroSleep,
);
}
$Self->Print("<green>Done.</green>\n");
return $Self->ExitCodeOk();
}
sub _CleanupLocks {
my ( $Self, %Param ) = @_;
my @Users = @{ $Param{InvalidUsers} };
$Self->Print( " Lock Cleanup for " . ( scalar @Users ) . " users starting...\n" );
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
my $StateMap = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::InvalidOwner::StateChange') // {};
my @TicketIDs = $TicketObject->TicketSearch(
Result => 'ARRAY',
Limit => 1_000_000,
OwnerIDs => [ map { $_->{UserID} } @Users ],
Locks => ['lock'],
UserID => 1,
);
my $StateCount = 0;
for my $TicketID (@TicketIDs) {
my %Ticket = $TicketObject->TicketGet(
TicketID => $TicketID,
UserID => 1,
);
$TicketObject->TicketLockSet(
Lock => 'unlock',
TicketID => $TicketID,
UserID => $Ticket{OwnerID},
SendNoNotification => 1,
);
if ( my $NewState = $StateMap->{ $Ticket{StateType} } ) {
my $StateSet = $TicketObject->TicketStateSet(
TicketID => $TicketID,
State => $NewState,
UserID => 1,
);
$StateCount++ if $StateSet;
}
Time::HiRes::usleep( $Param{MicroSleep} ) if $Param{MicroSleep};
}
$Self->Print(
" <green>Done</green> (unlocked <yellow>"
. @TicketIDs
. "</yellow> and changed state of <yellow>$StateCount</yellow> tickets).\n"
);
return;
}
sub _CleanupFlags {
my ( $Self, %Param ) = @_;
my @CleanupInvalidUsers = @{ $Param{CleanupInvalidUsers} };
$Self->Print( " Flag Cleanup for " . ( scalar @CleanupInvalidUsers ) . " users starting...\n" );
# get needed objects
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
for my $User (@CleanupInvalidUsers) {
$Self->Print(" Checking for tickets with seen flags for user <yellow>$User->{UserLogin}</yellow>...\n");
return if !$DBObject->Prepare(
SQL => "
SELECT DISTINCT(ticket.id)
FROM ticket
INNER JOIN ticket_flag ON ticket.id = ticket_flag.ticket_id
WHERE ticket_flag.create_by = $User->{UserID}
AND ticket_flag.ticket_key = 'Seen'",
Limit => 1_000_000,
);
my @TicketIDs;
while ( my @Row = $DBObject->FetchrowArray() ) {
push @TicketIDs, $Row[0];
}
my $Count = 0;
for my $TicketID (@TicketIDs) {
my $Delete = $TicketObject->TicketFlagDelete(
TicketID => $TicketID,
Key => 'Seen',
UserID => $User->{UserID},
);
$Count++ if $Delete;
Time::HiRes::usleep( $Param{MicroSleep} ) if $Param{MicroSleep};
}
$Self->Print(
" <green>Done</green> (changed <yellow>$Count</yellow> tickets for user <yellow>$User->{UserLogin}</yellow>).\n"
);
$Self->Print(" Checking for articles with seen flags for user <yellow>$User->{UserLogin}</yellow>...\n");
return if !$DBObject->Prepare(
SQL => "
SELECT DISTINCT(article.id), article.ticket_id
FROM article
INNER JOIN ticket ON ticket.id = article.ticket_id
INNER JOIN article_flag ON article.id = article_flag.article_id
WHERE article_flag.create_by = $User->{UserID}
AND article_flag.article_key = 'Seen'",
Limit => 1_000_000,
);
my @IDs;
while ( my @Row = $DBObject->FetchrowArray() ) {
push @IDs,
{
ArticleID => $Row[0],
TicketID => $Row[1],
};
}
$Count = 0;
for my $ID (@IDs) {
my $Delete = $ArticleObject->ArticleFlagDelete(
ArticleID => $ID->{ArticleID},
TicketID => $ID->{TicketID},
Key => 'Seen',
UserID => $User->{UserID},
);
$Count++ if $Delete;
Time::HiRes::usleep( $Param{MicroSleep} ) if $Param{MicroSleep};
}
$Self->Print(
" <green>Done</green> (changed <yellow>$Count</yellow> articles for user <yellow>$User->{UserLogin}</yellow>).\n"
);
if ( $ConfigObject->Get('Ticket::Watcher') ) {
$Self->Print(
" Checking for tickets with ticket watcher entries for user <yellow>$User->{UserLogin}</yellow>...\n"
);
return if !$DBObject->Prepare(
SQL => "
SELECT DISTINCT(ticket.id)
FROM ticket
INNER JOIN ticket_watcher ON ticket.id = ticket_watcher.ticket_id",
Limit => 1_000_000,
);
@TicketIDs = ();
while ( my @Row = $DBObject->FetchrowArray() ) {
push @TicketIDs, $Row[0];
}
my $Count = 0;
for my $TicketID (@TicketIDs) {
my $Unsubscribe = $TicketObject->TicketWatchUnsubscribe(
TicketID => $TicketID,
WatchUserID => $User->{UserID},
UserID => 1,
);
$Count++ if $$Unsubscribe;
Time::HiRes::usleep( $Param{MicroSleep} ) if $Param{MicroSleep};
}
$Self->Print(
" <green>Done</green> (changed <yellow>$Count</yellow> tickets for user <yellow>$User->{UserLogin}</yellow>).\n"
);
}
}
$Self->Print(" <green>Done.</green>\n");
return;
}
1;

View File

@@ -0,0 +1,172 @@
# --
# 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::Console::Command::Maint::Ticket::PendingCheck;
use strict;
use warnings;
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::DateTime',
'Kernel::System::State',
'Kernel::System::Ticket',
'Kernel::System::User',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description('Process pending tickets that are past their pending time and send pending reminders.');
return;
}
sub Run {
my ( $Self, %Param ) = @_;
$Self->Print("<yellow>Process pending tickets...</yellow>\n");
# get needed objects
my $StateObject = $Kernel::OM->Get('Kernel::System::State');
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
my @TicketIDs;
my @PendingAutoStateIDs = $StateObject->StateGetStatesByType(
Type => 'PendingAuto',
Result => 'ID',
);
if (@PendingAutoStateIDs) {
# do ticket auto jobs
@TicketIDs = $TicketObject->TicketSearch(
Result => 'ARRAY',
StateIDs => [@PendingAutoStateIDs],
SortBy => ['PendingTime'],
OrderBy => ['Up'],
UserID => 1,
);
my %States = %{ $Kernel::OM->Get('Kernel::Config')->Get('Ticket::StateAfterPending') };
TICKETID:
for my $TicketID (@TicketIDs) {
# get ticket data
my %Ticket = $TicketObject->TicketGet(
TicketID => $TicketID,
UserID => 1,
DynamicFields => 0,
);
next TICKETID if $Ticket{UntilTime} >= 1;
next TICKETID if !$States{ $Ticket{State} };
$Self->Print(
" Update ticket state for ticket $Ticket{TicketNumber} ($TicketID) to '$States{$Ticket{State}}'..."
);
# set new state
my $Success = $TicketObject->StateSet(
TicketID => $TicketID,
State => $States{ $Ticket{State} },
UserID => 1,
);
# error handling
if ( !$Success ) {
$Self->Print(" failed.\n");
next TICKETID;
}
# get state type for new state
my %State = $StateObject->StateGet(
Name => $States{ $Ticket{State} },
);
if ( $State{TypeName} eq 'closed' ) {
# set new ticket lock
$TicketObject->LockSet(
TicketID => $TicketID,
Lock => 'unlock',
UserID => 1,
Notification => 0,
);
}
$Self->Print(" done.\n");
}
}
else {
$Self->Print(" No pending auto StateIDs found!\n");
}
# do ticket reminder notification jobs
@TicketIDs = $TicketObject->TicketSearch(
Result => 'ARRAY',
StateType => 'pending reminder',
UserID => 1,
);
TICKETID:
for my $TicketID (@TicketIDs) {
# get ticket data
my %Ticket = $TicketObject->TicketGet(
TicketID => $TicketID,
UserID => 1,
DynamicFields => 0,
);
next TICKETID if $Ticket{UntilTime} >= 1;
# get used calendar
my $Calendar = $TicketObject->TicketCalendarGet(
%Ticket,
);
# check if it is during business hours, then send reminder
my $StopDTObject = $Kernel::OM->Create('Kernel::System::DateTime');
my $StartDTObject = $Kernel::OM->Create('Kernel::System::DateTime');
$StartDTObject->Subtract( Seconds => 10 * 60 );
my $CountedTime = $StartDTObject->Delta(
DateTimeObject => $StopDTObject,
ForWorkingTime => 1,
Calendar => $Calendar,
);
# error handling
if ( !$CountedTime || !$CountedTime->{AbsoluteSeconds} ) {
next TICKETID;
}
# trigger notification event
$TicketObject->EventHandler(
Event => 'NotificationPendingReminder',
Data => {
TicketID => $Ticket{TicketID},
CustomerMessageParams => {
TicketNumber => $Ticket{TicketNumber},
},
},
UserID => 1,
);
$TicketObject->EventHandlerTransaction();
}
$Self->Print("<green>Done.</green>\n");
return $Self->ExitCodeOk();
}
1;

View File

@@ -0,0 +1,81 @@
# --
# 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::Console::Command::Maint::Ticket::QueueIndexCleanup;
use strict;
use warnings;
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::DB',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description('Cleanup unneeded entries from StaticDB queue index.');
return;
}
sub PreRun {
my ( $Self, %Param ) = @_;
my $Module = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule');
if ( $Module =~ m{StaticDB} ) {
my $Error = "$Module is the active queue index, aborting.\n";
$Error .= "Use Maint::Ticket::QueueIndexRebuild to regenerate the active index.\n";
die $Error;
}
return;
}
sub Run {
my ( $Self, %Param ) = @_;
$Self->Print("<yellow>Cleaning up ticket queue index...</yellow>\n");
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
my $Records;
$DBObject->Prepare(
SQL => 'SELECT count(*) from ticket_index'
);
while ( my @Row = $DBObject->FetchrowArray() ) {
$Records += $Row[0];
}
$DBObject->Prepare(
SQL => 'SELECT count(*) from ticket_lock_index'
);
while ( my @Row = $DBObject->FetchrowArray() ) {
$Records += $Row[0];
}
if ( !$Records ) {
$Self->Print("<green>Queue index is already clean.</green>\n");
return $Self->ExitCodeOk();
}
$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => 'DELETE FROM ticket_index',
);
$DBObject->Do(
SQL => 'DELETE FROM ticket_lock_index',
);
$Self->Print("<green>Done ($Records records deleted).</green>\n");
return $Self->ExitCodeOk();
}
1;

View File

@@ -0,0 +1,40 @@
# --
# 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::Console::Command::Maint::Ticket::QueueIndexRebuild;
use strict;
use warnings;
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::System::Ticket',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description('Rebuild the ticket index for AgentTicketQueue.');
return;
}
sub Run {
my ( $Self, %Param ) = @_;
$Self->Print("<yellow>Rebuilding ticket index...</yellow>\n");
if ( $Kernel::OM->Get('Kernel::System::Ticket')->TicketAcceleratorRebuild() ) {
$Self->Print("<green>Done.</green>\n");
return $Self->ExitCodeOk();
}
return $Self->ExitCodeError();
}
1;

View File

@@ -0,0 +1,96 @@
# --
# 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::Console::Command::Maint::Ticket::RestoreFromArchive;
use strict;
use warnings;
use Time::HiRes();
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::Ticket',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description('Restore non-closed tickets from the ticket archive.');
$Self->AddOption(
Name => 'micro-sleep',
Description => "Specify microseconds to sleep after every ticket to reduce system load (e.g. 1000).",
Required => 0,
HasValue => 1,
ValueRegex => qr/^\d+$/smx,
);
return;
}
sub Run {
my ( $Self, %Param ) = @_;
$Self->Print("<yellow>Restoring tickets from ticket archive...</yellow>\n");
# disable ticket events
$Kernel::OM->Get('Kernel::Config')->{'Ticket::EventModulePost'} = {};
# check if archive system is activated
if ( !$Kernel::OM->Get('Kernel::Config')->Get('Ticket::ArchiveSystem') ) {
$Self->Print("<green>No action required. The archive system is disabled at the moment.</green>\n");
return $Self->ExitCodeOk();
}
# get ticket object
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
# get all tickets with an archive flag and an open statetype
my @TicketIDs = $TicketObject->TicketSearch(
StateType => [ 'new', 'open', 'pending reminder', 'pending auto' ],
ArchiveFlags => ['y'],
Result => 'ARRAY',
Limit => 100_000_000,
UserID => 1,
Permission => 'ro',
);
my $TicketNumber = scalar @TicketIDs;
my $Count = 1;
my $MicroSleep = $Self->GetOption('micro-sleep');
TICKETID:
for my $TicketID (@TicketIDs) {
# restore ticket from archive
$TicketObject->TicketArchiveFlagSet(
TicketID => $TicketID,
UserID => 1,
ArchiveFlag => 'n',
);
# output state
if ( $Count % 2000 == 0 ) {
my $Percent = int( $Count / ( $TicketNumber / 100 ) );
$Self->Print(
"<yellow>$Count</yellow> of <yellow>$#TicketIDs</yellow> processed (<yellow>$Percent %</yellow> done).\n"
);
}
$Count++;
Time::HiRes::usleep($MicroSleep) if $MicroSleep;
}
$Self->Print("<green>Done ($TicketNumber tickets restored).</green>\n");
return $Self->ExitCodeOk();
}
1;

View File

@@ -0,0 +1,67 @@
# --
# 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::Console::Command::Maint::Ticket::UnlockAll;
use strict;
use warnings;
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::System::DB',
'Kernel::System::Lock',
'Kernel::System::Ticket',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description('Unlock all tickets by force.');
return;
}
sub Run {
my ( $Self, %Param ) = @_;
$Self->Print("<yellow>Unlocking all tickets...</yellow>\n");
my @ViewableLockIDs = $Kernel::OM->Get('Kernel::System::Lock')->LockViewableLock( Type => 'ID' );
my @Tickets;
$Kernel::OM->Get('Kernel::System::DB')->Prepare(
SQL => "
SELECT st.tn, st.id
FROM ticket st
WHERE st.ticket_lock_id NOT IN ( ${\(join ', ', @ViewableLockIDs)} ) ",
);
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
push @Tickets, \@Row;
}
for (@Tickets) {
my @Row = @{$_};
$Self->Print(" Unlocking ticket id $Row[0]... ");
my $Unlock = $Kernel::OM->Get('Kernel::System::Ticket')->LockSet(
TicketID => $Row[1],
Lock => 'unlock',
UserID => 1,
);
if ($Unlock) {
$Self->Print("<green>done.</green>\n");
}
else {
$Self->Print("<red>failed.</red>\n");
}
}
$Self->Print("<green>Done.</green>\n");
return $Self->ExitCodeOk();
}
1;

View File

@@ -0,0 +1,65 @@
# --
# 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::Console::Command::Maint::Ticket::UnlockTicket;
use strict;
use warnings;
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::System::Ticket',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description('Unlock a single ticket by force.');
$Self->AddArgument(
Name => 'ticket-id',
Description => "Ticket to be unlocked by force.",
Required => 1,
ValueRegex => qr/\d+/smx,
);
return;
}
sub Run {
my ( $Self, %Param ) = @_;
my $TicketID = $Self->GetArgument('ticket-id');
$Self->Print("<yellow>Unlocking ticket $TicketID...</yellow>\n");
my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet(
TicketID => $TicketID,
Silent => 1,
);
if ( !%Ticket ) {
$Self->PrintError("Could not find ticket $TicketID.");
return $Self->ExitCodeError();
}
my $Unlock = $Kernel::OM->Get('Kernel::System::Ticket')->LockSet(
TicketID => $TicketID,
Lock => 'unlock',
UserID => 1,
);
if ( !$Unlock ) {
$Self->PrintError('Failed.');
return $Self->ExitCodeError();
}
$Self->Print("<green>Done.</green>\n");
return $Self->ExitCodeOk();
}
1;

View File

@@ -0,0 +1,106 @@
# --
# 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::Console::Command::Maint::Ticket::UnlockTimeout;
use strict;
use warnings;
use parent qw(Kernel::System::Console::BaseCommand);
our @ObjectDependencies = (
'Kernel::System::DateTime',
'Kernel::System::DB',
'Kernel::System::Lock',
'Kernel::System::State',
'Kernel::System::Ticket',
);
sub Configure {
my ( $Self, %Param ) = @_;
$Self->Description('Unlock tickets that are past their unlock timeout.');
return;
}
sub Run {
my ( $Self, %Param ) = @_;
$Self->Print("<yellow>Unlocking tickets that are past their unlock timeout...</yellow>\n");
my @UnlockStateIDs = $Kernel::OM->Get('Kernel::System::State')->StateGetStatesByType(
Type => 'Unlock',
Result => 'ID',
);
my @ViewableLockIDs = $Kernel::OM->Get('Kernel::System::Lock')->LockViewableLock( Type => 'ID' );
my @Tickets;
$Kernel::OM->Get('Kernel::System::DB')->Prepare(
SQL => "
SELECT st.tn, st.id, st.timeout, sq.unlock_timeout, st.sla_id, st.queue_id
FROM ticket st, queue sq
WHERE st.queue_id = sq.id
AND sq.unlock_timeout != 0
AND st.ticket_state_id IN ( ${\(join ', ', @UnlockStateIDs)} )
AND st.ticket_lock_id NOT IN ( ${\(join ', ', @ViewableLockIDs)} ) ",
);
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
push @Tickets, \@Row;
}
TICKET:
for (@Tickets) {
my @Row = @{$_};
# get used calendar
my $Calendar = $Kernel::OM->Get('Kernel::System::Ticket')->TicketCalendarGet(
QueueID => $Row[5],
SLAID => $Row[4],
);
my $StartDTObject = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
Epoch => $Row[2],
},
);
my $StopDTObject = $Kernel::OM->Create('Kernel::System::DateTime');
my $StartStopDelta = $StartDTObject->Delta(
DateTimeObject => $StopDTObject,
ForWorkingTime => 1,
Calendar => $Calendar,
);
my $CountedTime = $StartStopDelta ? $StartStopDelta->{AbsoluteSeconds} : 0;
next TICKET if $CountedTime < $Row[3] * 60;
$Self->Print(" Unlocking ticket id $Row[0]... ");
my $Unlock = $Kernel::OM->Get('Kernel::System::Ticket')->LockSet(
TicketID => $Row[1],
Lock => 'unlock',
UserID => 1,
);
if ($Unlock) {
$Self->Print("<green>done.</green>\n");
}
else {
$Self->Print("<red>failed.</red>\n");
}
}
$Self->Print("<green>Done.</green>\n");
return $Self->ExitCodeOk();
}
1;