#!/usr/bin/perl -wT
#
# ==========================================================================
#
# ZoneMinder Event Filter Script, $Date$, $Revision$
# Copyright (C) 2001-2008 Philip Coombes
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ==========================================================================

=head1 NAME

zmfilter.pl - ZoneMinder tool to filter events

=head1 SYNOPSIS

 zmfilter.pl [-f <filter name>,--filter=<filter name>] | -v, --version

=head1 DESCRIPTION

This script continuously monitors the recorded events for the given
monitor and applies any filters which would delete and/or upload
matching events.

=head1 OPTIONS

 -f{filter name}, --filter={filter name}  - The name of a specific filter to run
 -v, --version                            - Print ZoneMinder version

=cut
use strict;
use bytes;

# ==========================================================================
#
# These are the elements you can edit to suit your installation
#
# ==========================================================================

use constant START_DELAY => 5; # How long to wait before starting

# ==========================================================================
#
# You shouldn't need to change anything from here downwards
#
# ==========================================================================

# Include from system perl paths only
use ZoneMinder;
require ZoneMinder::Filter;
use DBI;
use POSIX;
use Time::HiRes qw/gettimeofday/;
#use Date::Manip;
use Getopt::Long;
use autouse 'Pod::Usage'=>qw(pod2usage);
use autouse 'Data::Dumper'=>qw(Dumper);

use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|)
             ? $Config{ZM_DIR_EVENTS}
             : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS})
;

logInit();
logSetSignal();

if ( $Config{ZM_OPT_UPLOAD} )
{
    # Comment these out if you don't have them and don't want to upload
    # or don't want to use that format
    if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" )
    {
        require Archive::Zip;
        import Archive::Zip qw( :ERROR_CODES :CONSTANTS );
    }
    else
    {
        require Archive::Tar;
    }
    if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" )
    {
        require Net::FTP;
    }
    else
    {
        require Net::SFTP::Foreign;
    }
}

if ( $Config{ZM_OPT_EMAIL} )
{
    if ( $Config{ZM_NEW_MAIL_MODULES} )
    {
        require MIME::Lite;
        require Net::SMTP;
    }
    else
    {
        require MIME::Entity;
    }
}

if ( $Config{ZM_OPT_MESSAGE} )
{
    if ( $Config{ZM_NEW_MAIL_MODULES} )
    {
        require MIME::Lite;
        require Net::SMTP;
    }
    else
    {
        require MIME::Entity;
    }
}

$| = 1;

$ENV{PATH}  = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};

my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL};
my $event_id = 0;
my $filter_parm = "";
my $version = 0;

#

GetOptions(
    'filter=s'      =>\$filter_parm,
    'version'       =>\$version
) or pod2usage(-exitstatus => -1);

if ( $version ) {
    print ZoneMinder::Base::ZM_VERSION . "\n";
    exit(0);
}

if ( ! EVENT_PATH ) {
    Error( "No event path defined. Config was $Config{ZM_DIR_EVENTS}\n" );
    die;
}

chdir( EVENT_PATH );

my $dbh = zmDbConnect();

if ( $filter_parm )
{
    Info( "Scanning for events using filter '$filter_parm'\n" );
}
else
{
    Info( "Scanning for events\n" );
}

if ( !$filter_parm )
{
    sleep( START_DELAY );
}

my $filters;
my $last_action = 0;

while( 1 )
{
    my $now = time;
    if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} )
    {
        Debug( "Reloading filters\n" );
        $last_action = $now;
        $filters = getFilters( $filter_parm );
    }

    foreach my $filter ( @$filters )
    {
        checkFilter( $filter );
    }

    last if ( $filter_parm );

    Debug( "Sleeping for $delay seconds\n" );
    sleep( $delay );
}

sub getFilters
{
    my $filter_name = shift;

    my @filters;
    my $sql = "SELECT * FROM Filters WHERE";
    if ( $filter_name )
    {
        $sql .= " Name = ? and";
    }
    else
    {
        $sql .= " Background = 1 and";
    }
    $sql .= "(  AutoArchive = 1
                or AutoVideo = 1
                or AutoUpload = 1
                or AutoEmail = 1
                or AutoMessage = 1
                or AutoExecute = 1
                or AutoDelete = 1
             ) ORDER BY Name";
    my $sth = $dbh->prepare_cached( $sql )
        or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
    my $res;
    if ( $filter_name )
    {
        $res = $sth->execute( $filter_name )
            or Fatal( "Can't execute '$sql': ".$sth->errstr() );
    }
    else
    {
        $res = $sth->execute()
            or Fatal( "Can't execute '$sql': ".$sth->errstr() );
    }
    FILTER: while( my $db_filter = $sth->fetchrow_hashref() )
    {
		my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter );
        Debug( "Found filter '$db_filter->{Name}'\n" );
		my $sql = $filter->Sql();

		if ( ! $sql ) {
			Error( "Error parsing Sql. skipping filter '$db_filter->{Name}'\n" );
			next FILTER;
		}
		push( @filters, $filter );
    }
    $sth->finish();
	Debug( "Got " . @filters . " filters" );
    return( \@filters );
}

sub checkFilter
{
    my $filter = shift;

    Debug( "Checking filter '$filter->{Name}'".
        ($filter->{AutoDelete}?", delete":"").
        ($filter->{AutoArchive}?", archive":"").
        ($filter->{AutoVideo}?", video":"").
        ($filter->{AutoUpload}?", upload":"").
        ($filter->{AutoEmail}?", email":"").
        ($filter->{AutoMessage}?", message":"").
        ($filter->{AutoExecute}?", execute":"").
        "\n"
    );

	foreach my $event ( $filter->Execute() ) {
        Debug( "Checking event $event->{Id}\n" );
        my $delete_ok = !undef;
$dbh->ping();
        if ( $filter->{AutoArchive} )
        {
            Info( "Archiving event $event->{Id}\n" );
            # Do it individually to avoid locking up the table for new events
            my $sql = "update Events set Archived = 1 where Id = ?";
            my $sth = $dbh->prepare_cached( $sql )
                or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
            my $res = $sth->execute( $event->{Id} )
                or Error( "Can't execute '$sql': ".$sth->errstr() );
        }
        if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} )
        {
            if ( !$event->{Videoed} )
            {
                $delete_ok = undef if ( !generateVideo( $filter, $event ) );
            }
        }
        if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} )
        {
            if ( !$event->{Emailed} )
            {
                $delete_ok = undef if ( !sendEmail( $filter, $event ) );
            }
        }
        if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} )
        {
            if ( !$event->{Messaged} )
            {
                $delete_ok = undef if ( !sendMessage( $filter, $event ) );
            }
        }
        if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} )
        {
            if ( !$event->{Uploaded} )
            {
                $delete_ok = undef if ( !uploadArchFile( $filter, $event ) );
            }
        }
        if ( $filter->{AutoExecute} )
        {
            if ( !$event->{Executed} )
            {
                $delete_ok = undef if ( !executeCommand( $filter, $event ) );
            }
        }
        if ( $filter->{AutoDelete} )
        {
            if ( $delete_ok )
            {
                Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId}\n" );
                # Do it individually to avoid locking up the table for new events
                my $sql = "delete from Events where Id = ?";
                my $sth = $dbh->prepare_cached( $sql )
                    or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
                my $res = $sth->execute( $event->{Id} )
                    or Fatal( "Can't execute '$sql': ".$sth->errstr() );

                if ( ! $Config{ZM_OPT_FAST_DELETE} )
                {
                    my $sql = "delete from Frames where EventId = ?";
                    my $sth = $dbh->prepare_cached( $sql )
                        or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
                    my $res = $sth->execute( $event->{Id} )
                        or Fatal( "Can't execute '$sql': ".$sth->errstr() );

                    $sql = "delete from Stats where EventId = ?";
                    $sth = $dbh->prepare_cached( $sql )
                        or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
                    $res = $sth->execute( $event->{Id} )
                        or Fatal( "Can't execute '$sql': ".$sth->errstr() );

                    deleteEventFiles( $event->{Id}, $event->{MonitorId} );
                }
            }
            else
            {
                Error( "Unable to delete event $event->{Id} as previous operations failed\n" );
            }
        }
    }
}

sub generateVideo
{
    my $filter = shift;
    my $event = shift;
    my $phone = shift;

    my $rate = $event->{DefaultRate}/100;
    my $scale = $event->{DefaultScale}/100;
    my $format;

    my @ffmpeg_formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} );
    my $default_video_format;
    my $default_phone_format;
    foreach my $ffmpeg_format( @ffmpeg_formats )
    {
        if ( $ffmpeg_format =~ /^(.+)\*\*$/ )
        {
            $default_phone_format = $1;
        }
        elsif ( $ffmpeg_format =~ /^(.+)\*$/ )
        {
            $default_video_format = $1;
        }
    }

    if ( $phone && $default_phone_format )
    {
        $format = $default_phone_format;
    }
    elsif ( $default_video_format )
    {
        $format = $default_video_format;
    }
    else
    {
        $format = $ffmpeg_formats[0];
    }

    my $command = $Config{ZM_PATH_BIN}."/zmvideo.pl -e "
                  .$event->{Id}." -r ".$rate." -s ".$scale." -f ".$format;
    my $output = qx($command);
    chomp( $output );
    my $status = $? >> 8;
    if ( $status || logDebugging() )
    {
        Debug( "Output: $output\n" );
    }
    if ( $status )
    {
        Error( "Video generation '$command' failed with status: $status\n" );
        if ( wantarray() )
        {
            return( undef, undef );
        }
        return( 0 );
    }
    else
    {
        my $sql = "update Events set Videoed = 1 where Id = ?";
        my $sth = $dbh->prepare_cached( $sql )
            or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
        my $res = $sth->execute( $event->{Id} )
            or Fatal( "Can't execute '$sql': ".$sth->errstr() );
        if ( wantarray() )
        {
            return( $format, $output );
        }
    }
    return( 1 );
}

sub uploadArchFile
{
    my $filter = shift;
    my $event = shift;

    if ( ! $Config{ZM_UPLOAD_HOST} )
    {
        Error( "Cannot upload archive as no upload host defined" );
        return( 0 );
    }

    my $archFile = $event->{MonitorName}.'-'.$event->{Id};
    my $archImagePath = getEventPath( $event )
                       ."/"
                       .(
                            ( $Config{ZM_UPLOAD_ARCH_ANALYSE} )
                            ? '{*analyse,*capture}'
                            : '*capture'
                        )
                       .".jpg"
    ;
    my @archImageFiles = glob($archImagePath);
    my $archLocPath;

    my $archError = 0;
    if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" )
    {
        $archFile .= '.zip';
        $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile;
        my $zip = Archive::Zip->new();
        Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" );

        my $status = &AZ_OK;
        foreach my $imageFile ( @archImageFiles )
        {
            Debug( "Adding $imageFile\n" );
            my $member = $zip->addFile( $imageFile );
            if ( !$member )
            {
                Error( "Unable to add image file $imageFile to zip archive $archLocPath" );
                $archError = 1;
                last;
            }
            $member->desiredCompressionMethod( $Config{ZM_UPLOAD_ARCH_COMPRESS}
                                               ? &COMPRESSION_DEFLATED
                                               : &COMPRESSION_STORED
            );
        }
        if ( !$archError )
        {
            $status = $zip->writeToFileNamed( $archLocPath );

            if ( $archError = ($status != &AZ_OK) )
            {
                Error( "Zip error: $status\n " );
            }
        }
        else
        {
            Error( "Error adding images to zip archive $archLocPath, not writing" );
        }
    }
    elsif ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "tar" )
    {
        if ( $Config{ZM_UPLOAD_ARCH_COMPRESS} )
        {
            $archFile .= '.tar.gz';
        }
        else
        {
            $archFile .= '.tar';
        }
        $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile;
        Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" );

        if ( $archError = !Archive::Tar->create_archive(
                                $archLocPath,
                                $Config{ZM_UPLOAD_ARCH_COMPRESS},
                                @archImageFiles
                          )
           )
        {
            Error( "Tar error: ".Archive::Tar->error()."\n " );
        }
    }

    if ( $archError )
    {
        return( 0 );
    }
    else
    {
        if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" )
        {
            Info( "Uploading to ".$Config{ZM_UPLOAD_HOST}." using FTP\n" );
            my $ftp = Net::FTP->new(
                $Config{ZM_UPLOAD_HOST},
                Timeout=>$Config{ZM_UPLOAD_TIMEOUT},
                Passive=>$Config{ZM_UPLOAD_FTP_PASSIVE},
                Debug=>$Config{ZM_UPLOAD_DEBUG}
            );
            if ( !$ftp )
            {
                Error( "Can't create FTP connection: $@" );
                return( 0 );
            }
            $ftp->login( $Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS} )
                or Error( "FTP - Can't login" );
            $ftp->binary()
                or Error( "FTP - Can't go binary" );
            $ftp->cwd( $Config{ZM_UPLOAD_REM_DIR} )
                or Error( "FTP - Can't cwd" )
                if ( $Config{ZM_UPLOAD_REM_DIR} );
            $ftp->put( $archLocPath )
                or Error( "FTP - Can't upload '$archLocPath'" );
            $ftp->quit()
                or Error( "FTP - Can't quit" );
        }
        else
        {
            my $host = $Config{ZM_UPLOAD_HOST};
            $host .= ":".$Config{ZM_UPLOAD_PORT}
                if $Config{ZM_UPLOAD_PORT};
            Info( "Uploading to ".$host." using SFTP\n" );
            my %sftpOptions = ( host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} );
            $sftpOptions{password} = $Config{ZM_UPLOAD_PASS}
                if $Config{ZM_UPLOAD_PASS};
            $sftpOptions{port} = $Config{ZM_UPLOAD_PORT}
                if $Config{ZM_UPLOAD_PORT};
            $sftpOptions{timeout} = $Config{ZM_UPLOAD_TIMEOUT}
                if $Config{ZM_UPLOAD_TIMEOUT};
            my @more_ssh_args;
            push @more_ssh_args, '-o'=>'StrictHostKeyChecking=no'
		if ! $Config{ZM_UPLOAD_STRICT};
            push @more_ssh_args, '-v'
                if $Config{ZM_UPLOAD_DEBUG};
            $sftpOptions{more} = [@more_ssh_args];
            my $sftp = Net::SFTP::Foreign->new( $Config{ZM_UPLOAD_HOST}, %sftpOptions );
            if ( $sftp->error )
            {
                Error( "Can't create SFTP connection: ".$sftp->error );
                return( 0 );
            }
            $sftp->setcwd( $Config{ZM_UPLOAD_REM_DIR} )
                or Error( "SFTP - Can't setcwd: ".$sftp->error )
                if $Config{ZM_UPLOAD_REM_DIR};
            $sftp->put( $archLocPath, $archFile )
                or Error( "SFTP - Can't upload '$archLocPath': ".$sftp->error );
        }
        unlink( $archLocPath );
        my $sql = "update Events set Uploaded = 1 where Id = ?";
        my $sth = $dbh->prepare_cached( $sql )
            or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
        my $res = $sth->execute( $event->{Id} )
            or Fatal( "Can't execute '$sql': ".$sth->errstr() );
    }
    return( 1 );
}

sub substituteTags
{
    my $text = shift;
    my $filter = shift;
    my $event = shift;
    my $attachments_ref = shift;

    # First we'd better check what we need to get
    # We have a filter and an event, do we need any more
    # monitor information?
    my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/;

    my $monitor = {};
    if ( $need_monitor )
    {
        my $db_now = strftime( "%Y-%m-%d %H:%M:%S", localtime() );
        my $sql = "SELECT
            M.Id,
            count(E.Id) as EventCount,
            count(if(E.Archived,1,NULL))
                as ArchEventCount,
            count(if(E.StartTime>'$db_now' - INTERVAL 1 HOUR && E.Archived = 0,1,NULL))
                as HourEventCount,
            count(if(E.StartTime>'$db_now' - INTERVAL 1 DAY && E.Archived = 0,1,NULL))
                as DayEventCount,
            count(if(E.StartTime>'$db_now' - INTERVAL 7 DAY && E.Archived = 0,1,NULL))
                as WeekEventCount,
            count(if(E.StartTime>'$db_now' - INTERVAL 1 MONTH && E.Archived = 0,1,NULL))
                as MonthEventCount
            FROM Monitors as M LEFT JOIN Events as E on E.MonitorId = M.Id
            WHERE MonitorId = ?
            GROUP BY E.MonitorId
            ORDER BY Id"
        ;
        my $sth = $dbh->prepare_cached( $sql )
            or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
        my $res = $sth->execute( $event->{MonitorId} )
            or Fatal( "Can't execute '$sql': ".$sth->errstr() );
        $monitor = $sth->fetchrow_hashref();
        $sth->finish();
        return() if ( !$monitor );
    }

    # Do we need the image information too?
    my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM)%/;
    my $first_alarm_frame;
    my $max_alarm_frame;
    my $max_alarm_score = 0;
    if ( $need_images )
    {
        my $sql = "SELECT * FROM Frames
                   WHERE EventId = ? AND Type = 'Alarm'
                   ORDER BY FrameId"
        ;
        my $sth = $dbh->prepare_cached( $sql )
            or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
        my $res = $sth->execute( $event->{Id} )
            or Fatal( "Can't execute '$sql': ".$sth->errstr() );
        while( my $frame = $sth->fetchrow_hashref() )
        {
            if ( !$first_alarm_frame )
            {
                $first_alarm_frame = $frame;
            }
            if ( $frame->{Score} > $max_alarm_score )
            {
                $max_alarm_frame = $frame;
                $max_alarm_score = $frame->{Score};
            }
        }
        $sth->finish();
    }

    my $url = $Config{ZM_URL};
    $text =~ s/%ZP%/$url/g;
    $text =~ s/%MN%/$event->{MonitorName}/g;
    $text =~ s/%MET%/$monitor->{EventCount}/g;
    $text =~ s/%MEH%/$monitor->{HourEventCount}/g;
    $text =~ s/%MED%/$monitor->{DayEventCount}/g;
    $text =~ s/%MEW%/$monitor->{WeekEventCount}/g;
    $text =~ s/%MEM%/$monitor->{MonthEventCount}/g;
    $text =~ s/%MEA%/$monitor->{ArchEventCount}/g;
    $text =~ s/%MP%/$url?view=watch&mid=$event->{MonitorId}/g;
    $text =~ s/%MPS%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=stream/g;
    $text =~ s/%MPI%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=still/g;
    $text =~ s/%EP%/$url?view=event&mid=$event->{MonitorId}&eid=$event->{Id}/g;
    $text =~ s/%EPS%/$url?view=event&mode=stream&mid=$event->{MonitorId}&eid=$event->{Id}/g;
    $text =~ s/%EPI%/$url?view=event&mode=still&mid=$event->{MonitorId}&eid=$event->{Id}/g;
    $text =~ s/%EI%/$event->{Id}/g;
    $text =~ s/%EN%/$event->{Name}/g;
    $text =~ s/%EC%/$event->{Cause}/g;
    $text =~ s/%ED%/$event->{Notes}/g;
    $text =~ s/%ET%/$event->{StartTime}/g;
    $text =~ s/%EL%/$event->{Length}/g;
    $text =~ s/%EF%/$event->{Frames}/g;
    $text =~ s/%EFA%/$event->{AlarmFrames}/g;
    $text =~ s/%EST%/$event->{TotScore}/g;
    $text =~ s/%ESA%/$event->{AvgScore}/g;
    $text =~ s/%ESM%/$event->{MaxScore}/g;
    if ( $first_alarm_frame )
    {
        $text =~ s/%EPI1%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$first_alarm_frame->{FrameId}/g;
        $text =~ s/%EPIM%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$max_alarm_frame->{FrameId}/g;
        if ( $attachments_ref && $text =~ s/%EI1%//g )
        {
            push( @$attachments_ref,
                {
                    type=>"image/jpeg",
                    path=>sprintf(
                        "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-capture.jpg",
                        getEventPath( $event ),
                        $first_alarm_frame->{FrameId}
                    )
                }
            );
        }
        if ( $attachments_ref && $text =~ s/%EIM%//g )
        {
            # Don't attach the same image twice
            if ( !@$attachments_ref
                 || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} )
            )
            {
                push( @$attachments_ref,
                    {
                        type=>"image/jpeg",
                        path=>sprintf(
                            "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-capture.jpg",
                            getEventPath( $event ),
                            $max_alarm_frame->{FrameId}
                        )
                    }
                );
            }
        }
    }
    if ( $attachments_ref && $Config{ZM_OPT_FFMPEG} )
    {
        if ( $text =~ s/%EV%//g )
        {
            my ( $format, $path ) = generateVideo( $filter, $event );
            if ( !$format )
            {
                return( undef );
            }
            push( @$attachments_ref, { type=>"video/$format", path=>$path } );
        }
        if ( $text =~ s/%EVM%//g )
        {
            my ( $format, $path ) = generateVideo( $filter, $event, 1 );
            if ( !$format )
            {
                return( undef );
            }
            push( @$attachments_ref, { type=>"video/$format", path=>$path } );
        }
    }
    $text =~ s/%FN%/$filter->{Name}/g;
    ( my $filter_name = $filter->{Name} ) =~ s/ /+/g;
    $text =~ s/%FP%/$url?view=filter&mid=$event->{MonitorId}&filter_name=$filter_name/g;

    return( $text );
}

sub sendEmail
{
    my $filter = shift;
    my $event = shift;

    if ( ! $Config{ZM_FROM_EMAIL} )
    {
        Error( "No 'from' email address defined, not sending email" );
        return( 0 );
    }
    if ( ! $Config{ZM_EMAIL_ADDRESS} )
    {
        Error( "No email address defined, not sending email" );
        return( 0 );
    }

    Info( "Creating notification email\n" );

    my $subject = substituteTags( $Config{ZM_EMAIL_SUBJECT}, $filter, $event );
    return( 0 ) if ( !$subject );
    my @attachments;
    my $body = substituteTags( $Config{ZM_EMAIL_BODY}, $filter, $event, \@attachments );
    return( 0 ) if ( !$body );

    Info( "Sending notification email '$subject'\n" );

    eval
    {
        if ( $Config{ZM_NEW_MAIL_MODULES} )
        {
            ### Create the multipart container
            my $mail = MIME::Lite->new (
                From => $Config{ZM_FROM_EMAIL},
                To => $Config{ZM_EMAIL_ADDRESS},
                Subject => $subject,
                Type => "multipart/mixed"
            );
            ### Add the text message part
            $mail->attach (
                Type => "TEXT",
                Data => $body
            );
            ### Add the attachments
            foreach my $attachment ( @attachments )
            {
                Info( "Attaching '$attachment->{path}\n" );
                $mail->attach(
                    Path => $attachment->{path},
                    Type => $attachment->{type},
                    Disposition => "attachment"
                );
            }
            ### Send the Message
            if ( $Config{ZM_SSMTP_MAIL} ) {
                my $ssmtp_location = $Config{ZM_SSMTP_PATH};
                if ( !$ssmtp_location ) {
                    if ( logDebugging() ) {
                        Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" );
                    }
                    $ssmtp_location = qx('which ssmtp');
                }
                if ( !$ssmtp_location ) {
                    Debug( "Can't find ssmtp, trying MIME::Lite->send" );
                    MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 );
                    $mail->send();
                } else {
                    ### Send using SSMTP
                    $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS} );
                }
            } else {
                MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 );
                $mail->send();
            }
        }
        else
        {
            my $mail = MIME::Entity->build(
                From => $Config{ZM_FROM_EMAIL},
                To => $Config{ZM_EMAIL_ADDRESS},
                Subject => $subject,
                Type => (($body=~/<html>/)?'text/html':'text/plain'),
                Data => $body
            );

            foreach my $attachment ( @attachments )
            {
                Info( "Attaching '$attachment->{path}\n" );
                $mail->attach(
                    Path => $attachment->{path},
                    Type => $attachment->{type},
                    Encoding => "base64"
                );
            }
            $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL} );
        }
    };
    if ( $@ )
    {
        Error( "Can't send email: $@" );
        return( 0 );
    }
    else
    {
        Info( "Notification email sent\n" );
    }
    my $sql = "update Events set Emailed = 1 where Id = ?";
    my $sth = $dbh->prepare_cached( $sql )
        or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
    my $res = $sth->execute( $event->{Id} )
        or Fatal( "Can't execute '$sql': ".$sth->errstr() );

    return( 1 );
}

sub sendMessage
{
    my $filter = shift;
    my $event = shift;

    if ( ! $Config{ZM_FROM_EMAIL} )
    {
        Error( "No 'from' email address defined, not sending message" );
        return( 0 );
    }
    if ( ! $Config{ZM_MESSAGE_ADDRESS} )
    {
        Error( "No message address defined, not sending message" );
        return( 0 );
    }

    Info( "Creating notification message\n" );

    my $subject = substituteTags( $Config{ZM_MESSAGE_SUBJECT}, $filter, $event );
    return( 0 ) if ( !$subject );
    my @attachments;
    my $body = substituteTags( $Config{ZM_MESSAGE_BODY}, $filter, $event, \@attachments );
    return( 0 ) if ( !$body );

    Info( "Sending notification message '$subject'\n" );

    eval
    {
        if ( $Config{ZM_NEW_MAIL_MODULES} )
        {
            ### Create the multipart container
            my $mail = MIME::Lite->new (
                From => $Config{ZM_FROM_EMAIL},
                To => $Config{ZM_MESSAGE_ADDRESS},
                Subject => $subject,
                Type => "multipart/mixed"
            );
            ### Add the text message part
            $mail->attach (
                Type => "TEXT",
                Data => $body
            );
            ### Add the attachments
            foreach my $attachment ( @attachments )
            {
                Info( "Attaching '$attachment->{path}\n" );
                $mail->attach(
                    Path => $attachment->{path},
                    Type => $attachment->{type},
                    Disposition => "attachment"
                );
            }
            ### Send the Message
            if ( $Config{ZM_SSMTP_MAIL} ) {
                my $ssmtp_location = $Config{ZM_SSMTP_PATH};
                if ( !$ssmtp_location ) {
                    if ( logDebugging() ) {
                        Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" );
                    }
                    $ssmtp_location = qx('which ssmtp');
                }
                if ( !$ssmtp_location ) {
                    Debug( "Can't find ssmtp, trying MIME::Lite->send" );
                    MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 );
                    $mail->send();
                } else {
                    ### Send using SSMTP
                    $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_MESSAGE_ADDRESS} );
                }
            } else {
                MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 );
                $mail->send();
            }
        }
        else
        {
            my $mail = MIME::Entity->build(
                From => $Config{ZM_FROM_EMAIL},
                To => $Config{ZM_MESSAGE_ADDRESS},
                Subject => $subject,
                Type => (($body=~/<html>/)?'text/html':'text/plain'),
                Data => $body
            );

            foreach my $attachment ( @attachments )
            {
                Info( "Attaching '$attachment->{path}\n" );
                $mail->attach(
                    Path => $attachment->{path},
                    Type => $attachment->{type},
                    Encoding => "base64"
                );
            }
            $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST},
                             MailFrom => $Config{ZM_FROM_EMAIL}
            );
        }
    };
    if ( $@ )
    {
        Error( "Can't send email: $@" );
        return( 0 );
    }
    else
    {
        Info( "Notification message sent\n" );
    }
    my $sql = "update Events set Messaged = 1 where Id = ?";
    my $sth = $dbh->prepare_cached( $sql )
        or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
    my $res = $sth->execute( $event->{Id} )
        or Fatal( "Can't execute '$sql': ".$sth->errstr() );

    return( 1 );
}

sub executeCommand
{
    my $filter = shift;
    my $event = shift;

    my $event_path = getEventPath( $event );

    my $command = $filter->{AutoExecuteCmd};
    $command .= " $event_path";

    Info( "Executing '$command'\n" );
    my $output = qx($command);
    my $status = $? >> 8;
    if ( $status || logDebugging() )
    {
        chomp( $output );
        Debug( "Output: $output\n" );
    }
    if ( $status )
    {
        Error( "Command '$command' exited with status: $status\n" );
        return( 0 );
    }
    else
    {
        my $sql = "update Events set Executed = 1 where Id = ?";
        my $sth = $dbh->prepare_cached( $sql )
            or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
        my $res = $sth->execute( $event->{Id} )
            or Fatal( "Can't execute '$sql': ".$sth->errstr() );
    }
    return( 1 );
}

