diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index ed81489da..8239af0d3 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -186,6 +186,7 @@ CREATE TABLE `Events` ( `Id` bigint unsigned NOT NULL auto_increment, `MonitorId` int(10) unsigned NOT NULL default '0', `StorageId` smallint(5) unsigned default 0, + `SecondaryStorageId` smallint(5) unsigned default 0, `Name` varchar(64) NOT NULL default '', `Cause` varchar(32) NOT NULL default '', `StartTime` datetime default NULL, diff --git a/db/zm_update-1.33.14.sql b/db/zm_update-1.33.14.sql new file mode 100644 index 000000000..83d0cfbba --- /dev/null +++ b/db/zm_update-1.33.14.sql @@ -0,0 +1,51 @@ +-- +-- Add CopyTo action to Filters +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'AutoCopy' + ) > 0, +"SELECT 'Column AutoCopy already exists in Filters'", +"ALTER TABLE Filters ADD `AutoCopy` tinyint(3) unsigned NOT NULL default '0' AFTER `AutoMove`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'AutoCopyTo' + ) > 0, +"SELECT 'Column AutoCopyTo already exists in Filters'", +"ALTER TABLE Filters ADD `AutoCopyTo` smallint(5) unsigned NOT NULL default '0' AFTER `AutoCopy`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'Query_json' + ) > 0, +"SELECT 'Column Query_json already exists in Filters'", +"ALTER TABLE `Filters` Change `Query` `Query_json` text NOT NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events' + AND column_name = 'SecondaryStorageId' + ) > 0, +"SELECT 'Column SecondaryStorageId already exists in Events'", +"ALTER TABLE `Events` ADD `SecondaryStorageId` smallint(5) unsigned default 0 AFTER `StorageId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index 1c85e704c..74a9ca81d 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -189,11 +189,17 @@ Add the following to the bottom of the file :: # Backports repository - deb http://httpredir.debian.org/debian jessie-backports main contrib non-free + deb http://archive.debian.org/debian/ jessie-backports main contrib non-free CTRL+o and to save CTRL+x to exit +Run the following + +:: + + echo 'Acquire::Check-Valid-Until no;' > /etc/apt/apt.conf.d/99no-check-valid-until + **Step 5:** Install ZoneMinder :: diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 9c6fe3cd5..fd1c8297d 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -41,6 +41,7 @@ require Number::Bytes::Human; require Date::Parse; require POSIX; use Date::Format qw(time2str); +use Time::HiRes qw(gettimeofday tv_interval); #our @ISA = qw(ZoneMinder::Object); use parent qw(ZoneMinder::Object); @@ -63,6 +64,7 @@ $serial = $primary_key = 'Id'; Id MonitorId StorageId + SecondaryStorageId Name Cause StartTime @@ -116,7 +118,7 @@ sub Time { } sub getPath { - return Path( @_ ); + return Path(@_); } sub Path { @@ -131,7 +133,7 @@ sub Path { if ( ! $$event{Path} ) { my $Storage = $event->Storage(); - $$event{Path} = join('/', $Storage->Path(), $event->RelativePath() ); + $$event{Path} = join('/', $Storage->Path(), $event->RelativePath()); } return $$event{Path}; } @@ -163,7 +165,8 @@ sub RelativePath { if ( $event->Time() ) { $$event{RelativePath} = join('/', $event->{MonitorId}, - POSIX::strftime( '%y/%m/%d/%H/%M/%S', + POSIX::strftime( + '%y/%m/%d/%H/%M/%S', localtime($event->Time()) ), ); @@ -203,7 +206,8 @@ sub LinkPath { if ( $event->Time() ) { $$event{LinkPath} = join('/', $event->{MonitorId}, - POSIX::strftime( '%y/%m/%d', + POSIX::strftime( + '%y/%m/%d', localtime($event->Time()) ), '.'.$$event{Id} @@ -255,8 +259,8 @@ sub createIdFile { sub GenerateVideo { my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_; - my $event_path = $self->Path( ); - chdir( $event_path ); + my $event_path = $self->Path(); + chdir($event_path); ( my $video_name = $self->{Name} ) =~ s/\s/_/g; my @file_parts; @@ -282,10 +286,10 @@ sub GenerateVideo { $file_scale =~ s/_00//; $file_scale =~ s/(_\d+)0+$/$1/; $file_scale = 's'.$file_scale; - push( @file_parts, $file_scale ); + push @file_parts, $file_scale; } elsif ( $size ) { my $file_size = 'S'.$size; - push( @file_parts, $file_size ); + push @file_parts, $file_size; } my $video_file = join('-', $video_name, $file_parts[0], $file_parts[1] ).'.'.$format; if ( $overwrite || !-s $video_file ) { @@ -393,61 +397,66 @@ sub delete { sub delete_files { my $event = shift; - my $Storage = @_ ? $_[0] : new ZoneMinder::Storage($$event{StorageId}); - my $storage_path = $Storage->Path(); + foreach my $Storage ( + @_ ? ($_[0]) : ( + new ZoneMinder::Storage($$event{StorageId}), + ( $$event{SecondaryStorageId} ? new ZoneMinder::Storage($$event{SecondaryStorageId}) : () ), + ) ) { + my $storage_path = $Storage->Path(); - if ( ! $storage_path ) { - Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId}"); - return; - } - - if ( ! $$event{MonitorId} ) { - Error("No monitor id assigned to event $$event{Id}"); - return; - } - my $event_path = $event->RelativePath(); - Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path, scheme is $$event{Scheme}."); - if ( $event_path ) { - ( $storage_path ) = ( $storage_path =~ /^(.*)$/ ); # De-taint - ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint - - my $deleted = 0; - if ( $$Storage{Type} and ( $$Storage{Type} eq 's3fs' ) ) { - my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$Storage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ ); - eval { - require Net::Amazon::S3; - my $s3 = Net::Amazon::S3->new( { - aws_access_key_id => $aws_id, - aws_secret_access_key => $aws_secret, - ( $aws_host ? ( host => $aws_host ) : () ), - }); - my $bucket = $s3->bucket($aws_bucket); - if ( ! $bucket ) { - Error("S3 bucket $bucket not found."); - die; - } - if ( $bucket->delete_key($event_path) ) { - $deleted = 1; - } else { - Error('Failed to delete from S3:'.$s3->err . ': ' . $s3->errstr); - } - }; - Error($@) if $@; + if ( ! $storage_path ) { + Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId}"); + return; } - if ( !$deleted ) { - my $command = "/bin/rm -rf $storage_path/$event_path"; - ZoneMinder::General::executeShellCommand($command); - } - } - if ( $event->Scheme() eq 'Deep' ) { - my $link_path = $event->LinkPath(); - Debug("Deleting link for Event $$event{Id} from $storage_path/$link_path."); - if ( $link_path ) { - ( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint + if ( ! $$event{MonitorId} ) { + Error("No monitor id assigned to event $$event{Id}"); + return; + } + my $event_path = $event->RelativePath(); + Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path, scheme is $$event{Scheme}."); + if ( $event_path ) { + ( $storage_path ) = ( $storage_path =~ /^(.*)$/ ); # De-taint + ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint + + my $deleted = 0; + if ( $$Storage{Type} and ( $$Storage{Type} eq 's3fs' ) ) { + my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$Storage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ ); + eval { + require Net::Amazon::S3; + my $s3 = Net::Amazon::S3->new( { + aws_access_key_id => $aws_id, + aws_secret_access_key => $aws_secret, + ( $aws_host ? ( host => $aws_host ) : () ), + }); + my $bucket = $s3->bucket($aws_bucket); + if ( ! $bucket ) { + Error("S3 bucket $bucket not found."); + die; + } + if ( $bucket->delete_key($event_path) ) { + $deleted = 1; + } else { + Error('Failed to delete from S3:'.$s3->err . ': ' . $s3->errstr); + } + }; + Error($@) if $@; + } + if ( !$deleted ) { + my $command = "/bin/rm -rf $storage_path/$event_path"; + ZoneMinder::General::executeShellCommand($command); + } + } # end if event_path + + if ( $event->Scheme() eq 'Deep' ) { + my $link_path = $event->LinkPath(); + Debug("Deleting link for Event $$event{Id} from $storage_path/$link_path."); + if ( $link_path ) { + ( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint unlink($storage_path.'/'.$link_path) or Error("Unable to unlink '$storage_path/$link_path': $!"); - } - } + } + } # end if Scheme eq Deep + } # end foreach Storage } # end sub delete_files sub StorageId { @@ -519,7 +528,7 @@ sub DiskSpace { return $_[0]{DiskSpace}; } -sub MoveTo { +sub CopyTo { my ( $self, $NewStorage ) = @_; my $OldStorage = $self->Storage(undef); @@ -531,9 +540,9 @@ sub MoveTo { # We do this before bothering to lock the event my ( $NewPath ) = ( $NewStorage->Path() =~ /^(.*)$/ ); # De-taint if ( ! $$NewStorage{Id} ) { - return "New storage does not have an id. Moving will not happen."; + return 'New storage does not have an id. Moving will not happen.'; } elsif ( $$NewStorage{Id} == $$self{StorageId} ) { - return "Event is already located at " . $NewPath; + return 'Event is already located at ' . $NewPath; } elsif ( !$NewPath ) { return "New path ($NewPath) is empty."; } elsif ( ! -e $NewPath ) { @@ -545,7 +554,7 @@ sub MoveTo { # data is reloaded, so need to check that the move hasn't already happened. if ( $$self{StorageId} == $$NewStorage{Id} ) { $ZoneMinder::Database::dbh->commit(); - return "Event has already been moved by someone else."; + return 'Event has already been moved by someone else.'; } if ( $$OldStorage{Id} != $$self{StorageId} ) { @@ -559,7 +568,7 @@ sub MoveTo { $ZoneMinder::Database::dbh->commit(); return "New path and old path are the same! $NewPath"; } - Debug("Moving event $$self{Id} from $OldPath to $NewPath"); + Debug("Copying event $$self{Id} from $OldPath to $NewPath"); my $moved = 0; @@ -580,17 +589,17 @@ sub MoveTo { } my $event_path = 'events/'.$self->RelativePath(); -Info("Making dir ectory $event_path/"); + Debug("Making directory $event_path/"); if ( ! $bucket->add_key( $event_path.'/','' ) ) { die "Unable to add key for $event_path/"; } my @files = glob("$OldPath/*"); -Debug("Files to move @files"); + Debug("Files to move @files"); for my $file (@files) { next if $file =~ /^\./; ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint - my $starttime = time; + my $starttime = [gettimeofday]; Debug("Moving file $file to $NewPath"); my $size = -s $file; if ( ! $size ) { @@ -602,10 +611,10 @@ Debug("Files to move @files"); } my $filename = $event_path.'/'.File::Basename::basename($file); - if ( ! $bucket->add_key( $filename, $file_contents ) ) { + if ( ! $bucket->add_key($filename, $file_contents) ) { die "Unable to add key for $filename"; } - my $duration = time - $starttime; + my $duration = tv_interval($starttime); Debug('PUT to S3 ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($duration?$size/$duration:$size) . '/sec'); } # end foreach file. @@ -616,13 +625,13 @@ Debug("Files to move @files"); } # end if s3 my $error = ''; - if ( ! $moved ) { - File::Path::make_path( $NewPath, {error => \my $err} ); + if ( !$moved ) { + File::Path::make_path($NewPath, {error => \my $err}); if ( @$err ) { for my $diag (@$err) { my ($file, $message) = %$diag; next if $message eq 'File exists'; - if ($file eq '') { + if ( $file eq '' ) { $error .= "general error: $message\n"; } else { $error .= "problem making $file: $message\n"; @@ -636,21 +645,21 @@ Debug("Files to move @files"); my @files = glob("$OldPath/*"); if ( ! @files ) { $ZoneMinder::Database::dbh->commit(); - return "No files to move."; + return 'No files to move.'; } for my $file (@files) { next if $file =~ /^\./; ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint - my $starttime = time; + my $starttime = [gettimeofday]; Debug("Moving file $file to $NewPath"); my $size = -s $file; if ( ! File::Copy::copy( $file, $NewPath ) ) { $error .= "Copy failed: for $file to $NewPath: $!"; last; } - my $duration = time - $starttime; - Debug("Copied " . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . ($duration?Number::Bytes::Human::format_bytes($size/$duration):'inf') . "/sec"); + my $duration = tv_interval($starttime); + Debug('Copied ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . ($duration?Number::Bytes::Human::format_bytes($size/$duration):'inf') . '/sec'); } # end foreach file. } # end if ! moved @@ -658,6 +667,15 @@ Debug("Files to move @files"); $ZoneMinder::Database::dbh->commit(); return $error; } +} # end sub CopyTo + +sub MoveTo { + + my ( $self, $NewStorage ) = @_; + my $OldStorage = $self->Storage(undef); + + my $error = $self->CopyTo($NewStorage); + return $error if $error; # Succeeded in copying all files, so we may now update the Event. $$self{StorageId} = $$NewStorage{Id}; @@ -667,10 +685,8 @@ Debug("Files to move @files"); $ZoneMinder::Database::dbh->commit(); return $error; } -Debug("Committing"); $ZoneMinder::Database::dbh->commit(); - $self->delete_files( $OldStorage ); -Debug("Done deleting files, returning"); + $self->delete_files($OldStorage); return $error; } # end sub MoveTo diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index fd6dd59dc..611bea059 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -132,11 +132,13 @@ sub Sql { my $self = shift; $$self{Sql} = shift if @_; if ( ! $$self{Sql} ) { - if ( !$self->{Query} ) { - Warning('No Query in filter.'); + $self->{Sql} = ''; + if ( ! $self->{Query_json} ) { + Warning("No query in Filter!"); return; } - my $filter_expr = ZoneMinder::General::jsonDecode($self->{Query}); + + my $filter_expr = ZoneMinder::General::jsonDecode($self->{Query_json}); my $sql = 'SELECT E.*, unix_timestamp(E.StartTime) as Time, M.Name as MonitorName, @@ -146,7 +148,6 @@ sub Sql { INNER JOIN Monitors as M on M.Id = E.MonitorId LEFT JOIN Storage as S on S.Id = E.StorageId '; - $self->{Sql} = ''; if ( $filter_expr->{terms} ) { foreach my $term ( @{$filter_expr->{terms}} ) { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Storage.pm b/scripts/ZoneMinder/lib/ZoneMinder/Storage.pm index 1f7c1b9fe..17d196f92 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Storage.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Storage.pm @@ -46,56 +46,13 @@ use ZoneMinder::Database qw(:all); use POSIX; -use vars qw/ $table $primary_key /; +use vars qw/ $table $primary_key %fields/; $table = 'Storage'; $primary_key = 'Id'; #__PACKAGE__->table('Storage'); #__PACKAGE__->primary_key('Id'); +%fields = map { $_ => $_ } qw( Id Name Path DoDelete ServerId Type Url DiskSpace Scheme ); -sub find { - shift if $_[0] eq 'ZoneMinder::Storage'; - my %sql_filters = @_; - - my $sql = 'SELECT * FROM Storage'; - my @sql_filters; - my @sql_values; - - if ( exists $sql_filters{Id} ) { - push @sql_filters , ' Id=? '; - push @sql_values, $sql_filters{Id}; - } - if ( exists $sql_filters{Name} ) { - push @sql_filters , ' Name = ? '; - push @sql_values, $sql_filters{Name}; - } - if ( exists $sql_filters{ServerId} ) { - push @sql_filters, ' ServerId = ?'; - push @sql_values, $sql_filters{ServerId}; - } - - - $sql .= ' WHERE ' . join(' AND ', @sql_filters) if @sql_filters; - $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; - - my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); - my $res = $sth->execute( @sql_values ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - my @results; - - while( my $db_filter = $sth->fetchrow_hashref() ) { - my $filter = new ZoneMinder::Storage( $$db_filter{Id}, $db_filter ); - push @results, $filter; - } # end while - Debug("SQL: $sql returned " . @results . ' results'); - return @results; -} - -sub find_one { - my @results = find(@_); - return $results[0] if @results; -} sub Path { if ( @_ > 1 ) { diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index b572d40fa..074cba1b5 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -240,6 +240,7 @@ sub getFilters { or AutoDelete = 1 or UpdateDiskSpace = 1 or AutoMove = 1 + or AutoCopy = 1 ) ORDER BY Name'; my $sth = $dbh->prepare_cached($sql) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); @@ -283,6 +284,7 @@ sub checkFilter { ($filter->{AutoMessage}?'message':()), ($filter->{AutoExecute}?'execute':()), ($filter->{AutoMove}?'move':()), + ($filter->{AutoCopy}?'copy':()), ($filter->{UpdateDiskSpace}?'update disk space':()), ), 'returned' , scalar @Events , 'events', @@ -300,9 +302,9 @@ sub checkFilter { Info("Archiving event $Event->{Id}"); # 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 ) + my $sth = $dbh->prepare_cached($sql) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); - my $res = $sth->execute( $Event->{Id} ) + my $res = $sth->execute($Event->{Id}) or Error("Unable to execute '$sql': ".$dbh->errstr()); } if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) { @@ -343,6 +345,23 @@ sub checkFilter { $_ = $Event->MoveTo($NewStorage); Error($_) if $_; } + if ( $filter->{AutoCopy} ) { + # Copy To is different from MoveTo in that it JUST copies the files + # So we still need to update the Event object with the new SecondaryStorageId + my $NewStorage = ZoneMinder::Storage->find_one(Id=>$filter->{AutoCopyTo}); + if ( $NewStorage ) { + $_ = $Event->CopyTo($NewStorage); + if ( $_ ) { + $ZoneMinder::Database::dbh->commit(); + Error($_); + } else { + $Event->save({SecondaryStorageId=>$$NewStorage{Id}}); + $ZoneMinder::Database::dbh->commit(); + } + } else { + Error("No storage area found for copy to operation. AutoCopyTo was $$filter{AutoCopyTo}"); + } + } # end if AutoCopy if ( $filter->{UpdateDiskSpace} ) { $ZoneMinder::Database::dbh->begin_work(); @@ -361,7 +380,7 @@ sub checkFilter { $ZoneMinder::Database::dbh->commit(); } # end if UpdateDiskSpace } # end foreach event -} +} # end sub checkFilter sub generateVideo { my $filter = shift; @@ -623,7 +642,7 @@ sub substituteTags { my $Monitor = $Event->Monitor() if $need_monitor; # Do we need the image information too? - my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA)%/; + my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA|EIMOD)%/; my $first_alarm_frame; my $max_alarm_frame; my $max_alarm_score = 0; diff --git a/version b/version index 91a50ee09..bd9492a0b 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.33.13 +1.33.14 diff --git a/web/includes/Event.php b/web/includes/Event.php index 1b996a839..dc7dd3575 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -12,6 +12,7 @@ class Event { 'Name', 'MonitorId', 'StorageId', +'SecondaryStorageId', 'Name', 'Cause', 'StartTime', @@ -85,6 +86,19 @@ class Event { return $this->{'Storage'}; } + public function SecondaryStorage( $new = null ) { + if ( $new ) { + $this->{'SecondaryStorage'} = $new; + } + if ( ! ( array_key_exists('SecondaryStorage', $this) and $this->{'SecondaryStorage'} ) ) { + if ( isset($this->{'SecondaryStorageId'}) and $this->{'SecondaryStorageId'} ) + $this->{'SecondaryStorage'} = Storage::find_one(array('Id'=>$this->{'SecondaryStorageId'})); + if ( ! ( array_key_exists('SecondaryStorage', $this) and $this->{'SecondaryStorage'} ) ) + $this->{'SecondaryStorage'} = new Storage(NULL); + } + return $this->{'SecondaryStorage'}; + } + public function Monitor() { if ( isset($this->{'MonitorId'}) ) { $Monitor = Monitor::find_one(array('Id'=>$this->{'MonitorId'})); diff --git a/web/includes/Filter.php b/web/includes/Filter.php index 8da602539..d59ecda7c 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -1,9 +1,11 @@ null, 'Name' => '', 'AutoExecute' => 0, @@ -16,68 +18,55 @@ public $defaults = array( 'AutoMessage' => 0, 'AutoMove' => 0, 'AutoMoveTo' => 0, + 'AutoCopy' => 0, + 'AutoCopyTo' => 0, 'UpdateDiskSpace' => 0, 'Background' => 0, 'Concurrent' => 0, - 'limit' => 100, - 'Query' => array(), - 'sort_field' => ZM_WEB_EVENT_SORT_FIELD, - 'sort_asc' => ZM_WEB_EVENT_SORT_ORDER, -); + 'Query_json' => '', + ); - public function __construct( $IdOrRow=NULL ) { - $row = NULL; - if ( $IdOrRow ) { - if ( is_integer($IdOrRow) or is_numeric($IdOrRow) ) { - $row = dbFetchOne('SELECT * FROM Filters WHERE Id=?', NULL, array($IdOrRow)); - if ( ! $row ) { - Error('Unable to load Filter record for Id=' . $IdOrRow); - } - } elseif ( is_array($IdOrRow) ) { - $row = $IdOrRow; - } else { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Error("Unknown argument passed to Filter Constructor from $file:$line)"); - Error("Unknown argument passed to Filter Constructor ($IdOrRow)"); - return; - } - } # end if isset($IdOrRow) + public function Query_json() { + if ( func_num_args( ) ) { + $this->{'Query_json'} = func_get_arg(0);; + $this->{'Query'} = jsonDecode($this->{'Query_json'}); + } + return $this->{'Query_json'}; + } - if ( $row ) { - foreach ($row as $k => $v) { - $this->{$k} = $v; - } - if ( array_key_exists('Query', $this) and $this->{'Query'} ) { - $this->{'Query'} = jsonDecode($this->{'Query'}); + public function Query() { + if ( func_num_args( ) ) { + $this->{'Query'} = func_get_arg(0);; + $this->{'Query_json'} = jsonEncode($this->{'Query'}); + } + if ( !array_key_exists('Query', $this) ) { + if ( array_key_exists('Query_json', $this) and $this->{'Query_json'} ) { + $this->{'Query'} = jsonDecode($this->{'Query_json'}); } else { $this->{'Query'} = array(); } - } - } // end function __construct - - public function __call( $fn, array $args ) { - if ( count( $args ) ) { - $this->{$fn} = $args[0]; - } - if ( array_key_exists( $fn, $this ) ) { - return $this->{$fn}; - } else if ( array_key_exists( $fn, $this->defaults ) ) { - $this->{$fn} = $this->defaults{$fn}; - return $this->{$fn}; } else { - - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Warning( "Unknown function call Filter->$fn from $file:$line" ); + if ( !is_array($this->{'Query'}) ) { + # Handle existence of both Query_json and Query in the row + $this->{'Query'} = jsonDecode($this->{'Query_json'}); + } } + return $this->{'Query'}; + } + + public static function find( $parameters = array(), $options = array() ) { + return ZM_Object::_find(get_class(), $parameters, $options); + } + + public static function find_one( $parameters = array(), $options = array() ) { + return ZM_Object::_find_one(get_class(), $parameters, $options); } public function terms( ) { - if ( func_num_args( ) ) { - $this->Query()['terms'] = func_get_arg(0); + if ( func_num_args() ) { + $Query = $this->Query(); + $Query['terms'] = func_get_arg(0); + $this->Query($Query); } if ( isset( $this->Query()['terms'] ) ) { return $this->Query()['terms']; @@ -88,106 +77,42 @@ public $defaults = array( // The following three fields are actually stored in the Query public function sort_field( ) { if ( func_num_args( ) ) { - $this->Query()['sort_field'] = func_get_arg(0); + $Query = $this->Query(); + $Query['sort_field'] = func_get_arg(0); + $this->Query($Query); } if ( isset( $this->Query()['sort_field'] ) ) { return $this->{'Query'}['sort_field']; } - return $this->defaults{'sort_field'}; + return ZM_WEB_EVENT_SORT_FIELD; + #return $this->defaults{'sort_field'}; } + public function sort_asc( ) { if ( func_num_args( ) ) { - $this->{'Query'}['sort_asc'] = func_get_arg(0); + $Query = $this->Query(); + $Query['sort_asc'] = func_get_arg(0); + $this->Query($Query); } if ( isset( $this->Query()['sort_asc'] ) ) { return $this->{'Query'}['sort_asc']; } - return $this->defaults{'sort_asc'}; + return ZM_WEB_EVENT_SORT_ORDER; + #return $this->defaults{'sort_asc'}; } + public function limit( ) { if ( func_num_args( ) ) { - $this->{'Query'}['limit'] = func_get_arg(0); + $Query = $this->Query(); + $Query['limit'] = func_get_arg(0); + $this->Query($Query); } if ( isset( $this->Query()['limit'] ) ) return $this->{'Query'}['limit']; - return $this->defaults{'limit'}; + return 100; + #return $this->defaults{'limit'}; } - public static function find( $parameters = null, $options = null ) { - $filters = array(); - $sql = 'SELECT * FROM Filters '; - $values = array(); - - if ( $parameters ) { - $fields = array(); - $sql .= 'WHERE '; - foreach ( $parameters as $field => $value ) { - if ( $value == null ) { - $fields[] = $field.' IS NULL'; - } else if ( is_array( $value ) ) { - $func = function(){return '?';}; - $fields[] = $field.' IN ('.implode(',', array_map($func, $value)). ')'; - $values += $value; - } else { - $fields[] = $field.'=?'; - $values[] = $value; - } - } - $sql .= implode(' AND ', $fields); - } - if ( $options ) { - if ( isset($options['order']) ) { - $sql .= ' ORDER BY ' . $options['order']; - } - if ( isset($options['limit']) ) { - if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { - $sql .= ' LIMIT ' . $options['limit']; - } else { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Error("Invalid value for limit(".$options['limit'].") passed to Filter::find from $file:$line"); - return array(); - } - } - } - $result = dbQuery($sql, $values); - $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Filter'); - foreach ( $results as $row => $obj ) { - $filters[] = $obj; - } - return $filters; - } # end find() - - public static function find_one( $parameters = array() ) { - $results = Filter::find($parameters, array('limit'=>1)); - if ( ! sizeof($results) ) { - return; - } - return $results[0]; - } # end find_one() - - public function delete() { - dbQuery('DELETE FROM Filters WHERE Id=?', array($this->{'Id'})); - } # end function delete() - - public function set( $data ) { - foreach ($data as $k => $v) { - if ( is_array( $v ) ) { - $this->{$k} = $v; - } else if ( is_string( $v ) ) { - $this->{$k} = trim( $v ); - } else if ( is_integer( $v ) ) { - $this->{$k} = $v; - } else if ( is_bool( $v ) ) { - $this->{$k} = $v; - } else { - Error( "Unknown type $k => $v of var " . gettype( $v ) ); - $this->{$k} = $v; - } - } - } # end function set - public function control($command, $server_id=null) { $Servers = $server_id ? Server::find(array('Id'=>$server_id)) : Server::find(); if ( !count($Servers) and !$server_id ) { diff --git a/web/includes/Object.php b/web/includes/Object.php new file mode 100644 index 000000000..2b58928d9 --- /dev/null +++ b/web/includes/Object.php @@ -0,0 +1,254 @@ + $v) { + $this->{$k} = $v; + } + $cache[$row['Id']] = $this; + } else { + # Set defaults + foreach ( $this->defaults as $k => $v ) $this->{$k} = $v; + } + } + + public function __call($fn, array $args){ + if ( count($args) ) { + $this->{$fn} = $args[0]; + } + if ( array_key_exists($fn, $this) ) { + return $this->{$fn}; + } else { + if ( array_key_exists($fn, $this->defaults) ) { + return $this->defaults{$fn}; + } else { + $backTrace = debug_backtrace(); + Warning("Unknown function call Sensor->$fn from ".print_r($backTrace,true)); + } + } + } + + public static function _find($class, $parameters = null, $options = null ) { + $table = $class::$table; + $filters = array(); + $sql = "SELECT * FROM `$table` "; + $values = array(); + + if ( $parameters ) { + $fields = array(); + $sql .= 'WHERE '; + foreach ( $parameters as $field => $value ) { + if ( $value == null ) { + $fields[] = '`'.$field.'` IS NULL'; + } else if ( is_array($value) ) { + $func = function(){return '?';}; + $fields[] = '`'.$field.'` IN ('.implode(',', array_map($func, $value)). ')'; + $values += $value; + + } else { + $fields[] = '`'.$field.'`=?'; + $values[] = $value; + } + } + $sql .= implode(' AND ', $fields ); + } + if ( $options ) { + if ( isset($options['order']) ) { + $sql .= ' ORDER BY ' . $options['order']; + } + if ( isset($options['limit']) ) { + if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { + $sql .= ' LIMIT ' . $options['limit']; + } else { + Error('Invalid value for limit('.$options['limit'].') passed to '.get_class()."::find from ".print_r($backTrace,true)); + return array(); + } + } + } + $rows = dbFetchAll($sql, NULL, $values); + $results = array(); + if ( $rows ) { + foreach ( $rows as $row ) { + array_push($results , new $class($row)); + } + } + return $results; + } # end public function find() + + public static function _find_one($class, $parameters = array(), $options = array() ) { + global $object_cache; + if ( ! isset($object_cache[$class]) ) + $object_cache[$class] = array(); + $cache = $object_cache[$class]; + if ( + ( count($parameters) == 1 ) and + isset($parameters['Id']) and + isset($cache[$parameters['Id']]) ) { + return $cache[$parameters['Id']]; + } + $options['limit'] = 1; + $results = ZM_Object::_find($class, $parameters, $options); + if ( ! sizeof($results) ) { + return; + } + return $results[0]; + } + + public static function Objects_Indexed_By_Id($class) { + $results = array(); + foreach ( ZM_Object::_find($class, null, array('order'=>'lower(Name)')) as $Object ) { + $results[$Object->Id()] = $Object; + } + return $results; + } + + public function to_json() { + $json = array(); + foreach ($this->defaults as $key => $value) { + if ( is_callable(array($this, $key)) ) { + $json[$key] = $this->$key(); + } else if ( array_key_exists($key, $this) ) { + $json[$key] = $this->{$key}; + } else { + $json[$key] = $this->defaults{$key}; + } + } + return json_encode($json); + } + + public function set($data) { + foreach ( $data as $k => $v ) { + if ( method_exists($this, $k) ) { + $this->{$k}($v); + } else { + if ( is_array($v) ) { +# perhaps should turn into a comma-separated string + $this->{$k} = implode(',', $v); + } else if ( is_string($v) ) { + if ( $v == '' and array_key_exists($k, $this->defaults) ) { + $this->{$k} = $this->defaults[$k]; + } else { + $this->{$k} = trim($v); + } + } else if ( is_integer($v) ) { + $this->{$k} = $v; + } else if ( is_bool($v) ) { + $this->{$k} = $v; + } else if ( is_null($v) ) { + $this->{$k} = $v; + } else { + Error( "Unknown type $k => $v of var " . gettype( $v ) ); + $this->{$k} = $v; + } + } # end if method_exists + } # end foreach $data as $k=>$v + } + + public function changes( $new_values ) { + $changes = array(); + foreach ( $new_values as $field => $value ) { + + if ( method_exists($this, $field) ) { + $old_value = $this->$field(); + Logger::Debug("Checking method $field () ".print_r($old_value,true)." => " . print_r($value,true)); + if ( is_array($old_value) ) { + $diff = array_recursive_diff($old_value, $value); + Logger::Debug("Checking method $field () diff is".print_r($diff,true)); + if ( count($diff) ) { + $changes[$field] = $value; + } + } else if ( $this->$field() != $value ) { + $changes[$field] = $value; + } + } else if ( array_key_exists($field, $this) ) { + Logger::Debug("Checking field $field => ".$this->{$field} . " ?= " .$value); + if ( $this->{$field} != $value ) { + $changes[$field] = $value; + } + } else if ( array_key_exists($field, $this->defaults) ) { + + Logger::Debug("Checking default $field => ".$this->defaults[$field] . " " .$value); + if ( $this->defaults[$field] != $value ) { + $changes[$field] = $value; + } + } + + #if ( (!array_key_exists($field, $this)) or ( $this->{$field} != $new_values[$field] ) ) { + #Logger::Debug("Checking default $field => $default_value changes becaause" . $new_values[$field].' != '.$new_values[$field]); + #$changes[$field] = $new_values[$field]; + ##} else if { + #Logger::Debug("Checking default $field => $default_value changes becaause " . $new_values[$field].' != '.$new_values[$field]); + ##array_push( $changes, [$field=>$defaults[$field]] ); + #} + #} else { + #Logger::Debug("Checking default $field => $default_value not in new_values"); + #} + } # end foreach default + return $changes; + } # end public function changes + + public function save($new_values = null) { + $class = get_class($this); + $table = $class::$table; + + if ( $new_values ) { + Logger::Debug("New values" . print_r($new_values,true)); + $this->set($new_values); + } + + if ( $this->Id() ) { + $fields = array_keys($this->defaults); + $sql = 'UPDATE '.$table.' SET '.implode(', ', array_map(function($field) {return '`'.$field.'`=?';}, $fields )) . ' WHERE Id=?'; + $values = array_map(function($field){return $this->{$field};}, $fields); + $values[] = $this->{'Id'}; + if ( dbQuery($sql, $values) ) + return true; + } else { + $fields = $this->defaults; + unset($fields['Id']); + + $sql = 'INSERT INTO '.$table.' ('.implode(', ', array_map(function($field) {return '`'.$field.'`';}, array_keys($fields))).') VALUES ('.implode(', ', array_map(function($field){return '?';}, array_values($fields))).')'; + $values = array_map(function($field){return $this->{$field};}, array_keys($fields)); + if ( dbQuery($sql, $values) ) { + $this->{'Id'} = dbInsertId(); + return true; + } + } + return false; + } // end function save + + public function delete() { + $class = get_class($this); + $table = $class::$table; + dbQuery("DELETE FROM $table WHERE Id=?", array($this->{'Id'})); + if ( isset($object_cache[$class]) and isset($object_cache[$class][$this->{'Id'}]) ) + unset($object_cache[$class][$this->{'Id'}]); + } + +} # end class Sensor Action +?> diff --git a/web/includes/Storage.php b/web/includes/Storage.php index b3caa3ffa..8607b52fe 100644 --- a/web/includes/Storage.php +++ b/web/includes/Storage.php @@ -2,10 +2,11 @@ namespace ZM; require_once('database.php'); require_once('Event.php'); +require_once('Object.php'); -$storage_cache = array(); -class Storage { - private $defaults = array( +class Storage extends ZM_Object { + protected static $table = 'Storage'; + protected $defaults = array( 'Id' => null, 'Path' => '', 'Name' => '', @@ -16,31 +17,12 @@ class Storage { 'ServerId' => 0, 'DoDelete' => 1, ); + public static function find($parameters = array(), $options = array() ) { + return ZM_Object::_find(get_class(), $parameters, $options); + } - public function __construct( $IdOrRow = NULL ) { - global $storage_cache; - - $row = NULL; - if ( $IdOrRow ) { - if ( is_integer($IdOrRow) or is_numeric($IdOrRow) ) { - $row = dbFetchOne('SELECT * FROM Storage WHERE Id=?', NULL, array($IdOrRow)); - if ( ! $row ) { - Error('Unable to load Storage record for Id=' . $IdOrRow); - } - } else if ( is_array($IdOrRow) ) { - $row = $IdOrRow; - } - } - if ( $row ) { - foreach ($row as $k => $v) { - $this->{$k} = $v; - } - $storage_cache[$row['Id']] = $this; - } else { - $this->{'Name'} = ''; - $this->{'Path'} = ''; - $this->{'Type'} = 'local'; - } + public static function find_one( $parameters = array(), $options = array() ) { + return ZM_Object::_find_one(get_class(), $parameters, $options); } public function Path() { @@ -66,93 +48,6 @@ class Storage { return $this->{'Name'}; } - public function __call( $fn, array $args= NULL ) { - if ( count($args) ) { - $this->{$fn} = $args[0]; - } - if ( array_key_exists($fn, $this) ) - return $this->{$fn}; - - if ( array_key_exists($fn, $this->defaults) ) - return $this->defaults{$fn}; - - $backTrace = debug_backtrace(); - $file = $backTrace[0]['file']; - $line = $backTrace[0]['line']; - Warning("Unknown function call Storage->$fn from $file:$line"); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Warning("Unknown function call Storage->$fn from $file:$line"); - } - - public static function find_one( $parameters = null, $options = null ) { - global $storage_cache; - if ( - ( count($parameters) == 1 ) and - isset($parameters['Id']) and - isset($storage_cache[$parameters['Id']]) ) { - return $storage_cache[$parameters['Id']]; - } - - $results = Storage::find($parameters, $options); - if ( count($results) > 1 ) { - Error('Storage Returned more than 1'); - return $results[0]; - } else if ( count($results) ) { - return $results[0]; - } else { - return null; - } - } - public static function find( $parameters = null, $options = null ) { - $sql = 'SELECT * FROM Storage '; - $values = array(); - - if ( $parameters ) { - $fields = array(); - $sql .= 'WHERE '; - foreach ( $parameters as $field => $value ) { - if ( $value == null ) { - $fields[] = $field.' IS NULL'; - } else if ( is_array($value) ) { - $func = function(){return '?';}; - $fields[] = $field.' IN ('.implode(',', array_map($func, $value)).')'; - $values += $value; - - } else { - $fields[] = $field.'=?'; - $values[] = $value; - } - } - $sql .= implode(' AND ', $fields); - } # end if parameters - if ( $options ) { - if ( isset($options['order']) ) { - $sql .= ' ORDER BY ' . $options['order']; - } # end if options - if ( isset($options['limit']) ) { - if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { - $sql .= ' LIMIT ' . $option['limit']; - } else { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Error("Invalid value for limit(".$options['limit'].") passed to Control::find from $file:$line"); - return array(); - } - } # end if limit - } # end if options - $storage_areas = array(); - $result = dbQuery($sql, $values); - if ( $result ) { - $results = $result->fetchALL(); - foreach ( $results as $row ) { - $storage_areas[] = new Storage($row); - } - } - return $storage_areas; - } # end find() - public function disk_usage_percent() { $path = $this->Path(); if ( ! $path ) { @@ -226,18 +121,5 @@ class Storage { return $this->{'Server'}; } - public function to_json() { - $json = array(); - foreach ($this->defaults as $key => $value) { - if ( is_callable(array($this, $key)) ) { - $json[$key] = $this->$key(); - } else if ( array_key_exists($key, $this) ) { - $json[$key] = $this->{$key}; - } else { - $json[$key] = $this->defaults{$key}; - } - } - return json_encode($json); - } } // end class Storage ?> diff --git a/web/includes/actions/filter.php b/web/includes/actions/filter.php index 6b3019ee5..8548f21d1 100644 --- a/web/includes/actions/filter.php +++ b/web/includes/actions/filter.php @@ -51,11 +51,30 @@ if ( isset($_REQUEST['object']) and ( $_REQUEST['object'] == 'filter' ) ) { $_REQUEST['filter']['Query']['sort_asc'] = validStr($_REQUEST['filter']['Query']['sort_asc']); $_REQUEST['filter']['Query']['limit'] = validInt($_REQUEST['filter']['Query']['limit']); if ( $action == 'execute' ) { - $tempFilterName = '_TempFilter'.time(); - $sql .= ' Name = \''.$tempFilterName.'\''; - } else { - $sql .= ' Name = '.dbEscape($_REQUEST['filter']['Name']); + $_REQUEST['filter']['Name'] = '_TempFilter'.time(); + unset($_REQUEST['Id']); + #$tempFilterName = '_TempFilter'.time(); + #$sql .= ' Name = \''.$tempFilterName.'\''; + #} else { + #$sql .= ' Name = '.dbEscape($_REQUEST['filter']['Name']); } + + $_REQUEST['filter']['AutoCopy'] = empty($_REQUEST['filter']['AutoCopy']) ? 0 : 1; + $_REQUEST['filter']['AutoMove'] = empty($_REQUEST['filter']['AutoMove']) ? 0 : 1; + $_REQUEST['filter']['AutoArchive'] = empty($_REQUEST['filter']['AutoArchive']) ? 0 : 1; + $_REQUEST['filter']['AutoVideo'] = empty($_REQUEST['filter']['AutoVideo']) ? 0 : 1; + $_REQUEST['filter']['AutoUpload'] = empty($_REQUEST['filter']['AutoUpload']) ? 0 : 1; + $_REQUEST['filter']['AutoEmail'] = empty($_REQUEST['filter']['AutoEmail']) ? 0 : 1; + $_REQUEST['filter']['AutoMessage'] = empty($_REQUEST['filter']['AutoMessage']) ? 0 : 1; + $_REQUEST['filter']['AutoExecute'] = empty($_REQUEST['filter']['AutoExecute']) ? 0 : 1; + $_REQUEST['filter']['AutoDelete'] = empty($_REQUEST['filter']['AutoDelete']) ? 0 : 1; + $_REQUEST['filter']['UpdateDiskSpace'] = empty($_REQUEST['filter']['UpdateDiskSpace']) ? 0 : 1; + $_REQUEST['filter']['Background'] = empty($_REQUEST['filter']['Background']) ? 0 : 1; + $_REQUEST['filter']['Concurrent'] = empty($_REQUEST['filter']['Concurrent']) ? 0 : 1; + $changes = $filter->changes($_REQUEST['filter']); + ZM\Logger::Debug("Changes: " . print_r($changes,true)); + + if ( 0 ) { $sql .= ', Query = '.dbEscape(jsonEncode($_REQUEST['filter']['Query'])); $sql .= ', AutoArchive = '.(!empty($_REQUEST['filter']['AutoArchive']) ? 1 : 0); $sql .= ', AutoVideo = '. ( !empty($_REQUEST['filter']['AutoVideo']) ? 1 : 0); @@ -73,17 +92,25 @@ if ( isset($_REQUEST['object']) and ( $_REQUEST['object'] == 'filter' ) ) { $sql .= ', UpdateDiskSpace = '. ( !empty($_REQUEST['filter']['UpdateDiskSpace']) ? 1 : 0); $sql .= ', Background = '. ( !empty($_REQUEST['filter']['Background']) ? 1 : 0); $sql .= ', Concurrent = '. ( !empty($_REQUEST['filter']['Concurrent']) ? 1 : 0); + } if ( $_REQUEST['Id'] and ( $action == 'Save' ) ) { + if ( 0 ) { dbQuery('UPDATE Filters SET '.$sql.' WHERE Id=?', array($_REQUEST['Id'])); + } + $filter->save($changes); if ( $filter->Background() ) $filter->control('stop'); } else { + # COuld be execute + if ( 0 ) { dbQuery('INSERT INTO Filters SET'.$sql); $_REQUEST['Id'] = dbInsertId(); $filter = new ZM\Filter($_REQUEST['Id']); + } + $filter->save($changes); } - if ( !empty($_REQUEST['filter']['Background']) ) + if ( $filter->Background() ) $filter->control('start'); if ( $action == 'execute' ) { diff --git a/web/includes/functions.php b/web/includes/functions.php index 867861ffe..9e0655933 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -2518,4 +2518,45 @@ function format_duration($time, $separator=':') { return sprintf('%02d%s%02d%s%02d', floor($time/3600), $separator, ($time/60)%60, $separator, $time%60); } +function array_recursive_diff($aArray1, $aArray2) { + $aReturn = array(); + + foreach ($aArray1 as $mKey => $mValue) { + if ( array_key_exists($mKey, $aArray2) ) { + if ( is_array($mValue) ) { + $aRecursiveDiff = array_recursive_diff($mValue, $aArray2[$mKey]); + if ( count($aRecursiveDiff) ) { + $aReturn[$mKey] = $aRecursiveDiff; + } + } else { + if ( $mValue != $aArray2[$mKey] ) { + $aReturn[$mKey] = $mValue; + } + } + } else { + $aReturn[$mKey] = $mValue; + } + } + # Now check for keys in array2 that are not in array1 + foreach ($aArray2 as $mKey => $mValue) { + if ( array_key_exists($mKey, $aArray1) ) { + # Already checked it... I think. + #if ( is_array($mValue) ) { + #$aRecursiveDiff = array_recursive_diff($mValue, $aArray2[$mKey]); + #if ( count($aRecursiveDiff) ) { + #$aReturn[$mKey] = $aRecursiveDiff; + #} + #} else { + #if ( $mValue != $aArray2[$mKey] ) { + #$aReturn[$mKey] = $mValue; + #} + #} + } else { + $aReturn[$mKey] = $mValue; + } + } + + return $aReturn; +} + ?> diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 4001c456e..0e575fd1f 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -132,6 +132,7 @@ $SLANG = array( 'AttrMaxScore' => 'Max. Score', 'AttrMonitorId' => 'Monitor Id', 'AttrMonitorName' => 'Monitor Name', + 'AttrSecondaryStorageArea' => 'Secondary Storage Area', 'AttrStorageArea' => 'Storage Area', 'AttrFilterServer' => 'Server Filter is Running On', 'AttrMonitorServer' => 'Server Monitor is Running On', @@ -356,6 +357,7 @@ $SLANG = array( 'FilterArchiveEvents' => 'Archive all matches', 'FilterUpdateDiskSpace' => 'Update used disk space', 'FilterDeleteEvents' => 'Delete all matches', + 'FilterCopyEvents' => 'Copy all matches', 'FilterMoveEvents' => 'Move all matches', 'FilterEmailEvents' => 'Email details of all matches', 'FilterExecuteEvents' => 'Execute command on all matches', diff --git a/web/skins/classic/views/event.php b/web/skins/classic/views/event.php index b09f88acc..f0a6e343c 100644 --- a/web/skins/classic/views/event.php +++ b/web/skins/classic/views/event.php @@ -134,7 +134,11 @@ if ( ! $Event->Id() ) { Length().'s' ?> Frames() ?>/AlarmFrames() ?> TotScore() ?>/AvgScore() ?>/MaxScore() ?> - DiskSpace(null)) . ' on ' . $Event->Storage()->Name() ?> + +DiskSpace(null)) . ' on ' . $Event->Storage()->Name(). + ( $Event->SecondaryStorageId() ? ', ' . $Event->SecondaryStorage()->Name() :'' ) +?>