diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 240f07153..b7546a656 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -811,6 +811,7 @@ INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-423','Ffmpeg','Reolink',0,0,1,0 INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-411','Ffmpeg','Reolink',0,0,1,0,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-420','Ffmpeg','Reolink',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'D-Link DCS-5020L','Remote','DCS5020L',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,24,1,0,1,1,1,0,1,0,1,0,0,1,30,0,0,0,0,0,1,0,0,1,30,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'IOS Camera','Ffmpeg','IPCAMIOS',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Dericam P2','Ffmpeg','DericamP2',0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,45,0,0,1,0,0,0,0,1,1,45,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Trendnet','Remote','Trendnet',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); @@ -845,6 +846,7 @@ INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, multicast','Remote','rt INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP','Remote','rtsp',0,255,'rtsp','rtpRtsp','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP/HTTP','Remote',NULL,NULL,NULL,'rtsp','rtpRtspHttp','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'D-link DCS-930L, 640x480, mjpeg','Remote','http',0,0,'http','simple','',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,'D-Link DCS-5020L, 640x480, mjpeg','Remote','http',0,0,'http','simple',':@','80','/video.cgi',NULL,640,480,0,NULL,1,'34',NULL,':@',100,100); INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100); diff --git a/db/zm_update-1.34.1.sql b/db/zm_update-1.34.1.sql new file mode 100644 index 000000000..65ba2e5ff --- /dev/null +++ b/db/zm_update-1.34.1.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.0 database to 1.34.1 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.2.sql b/db/zm_update-1.34.2.sql new file mode 100644 index 000000000..1fcc882d2 --- /dev/null +++ b/db/zm_update-1.34.2.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.1 database to 1.34.2 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.3.sql b/db/zm_update-1.34.3.sql new file mode 100644 index 000000000..b84207047 --- /dev/null +++ b/db/zm_update-1.34.3.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.2 database to 1.34.3 +-- +-- No changes required +-- diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 345139284..7297648a9 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -28,7 +28,7 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.34.0 +Version: 1.34.3 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons @@ -416,6 +416,12 @@ EOF %dir %attr(755,nginx,nginx) %{_localstatedir}/spool/zoneminder-upload %changelog +* Tue Feb 04 2020 Andrew Bauer - 1.34.2-1 +- 1.34.2 Release + +* Fri Jan 31 2020 Andrew Bauer - 1.34.1-1 +- 1.34.1 Release + * Sat Jan 18 2020 Andrew Bauer - 1.34.0-1 - 1.34.0 Release diff --git a/docs/api.rst b/docs/api.rst index 2aaeafb3e..76eadcaa3 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -127,7 +127,7 @@ If you are using the old credentials mechanism present in v1.0, then the credent Key lifetime (v2.0) ^^^^^^^^^^^^^^^^^^^^^^ -In version 2.0, it is easy to know when a key will expire before you use it. You can find that out from the ``access_token_expires`` and ``refresh_token_exipres`` values (in seconds) after you decode the JWT key (there are JWT decode libraries for every language you want). You should refresh the keys before the timeout occurs, or you will not be able to use the APIs. +In version 2.0, it is easy to know when a key will expire before you use it. You can find that out from the ``access_token_expires`` and ``refresh_token_expires`` values (in seconds) after you decode the JWT key (there are JWT decode libraries for every language you want). You should refresh the keys before the timeout occurs, or you will not be able to use the APIs. Understanding access/refresh tokens (v2.0) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/userguide/gettingstarted.rst b/docs/userguide/gettingstarted.rst index 509f413a2..226da8a41 100644 --- a/docs/userguide/gettingstarted.rst +++ b/docs/userguide/gettingstarted.rst @@ -109,7 +109,7 @@ This brings up the new monitor window: * In this example, the Function is 'Modect', which means it will start recording if motion is detected on that camera feed. The parameters for what constitutes motion detected is specific in :doc:`definezone` -* In Analytis FPS, we've put in 5FPS here. Note that you should not put an FPS that is greater than the camera FPS. In my case, 5FPS is sufficient for my needs +* In Analysis FPS, we've put in 5FPS here. Note that you should not put an FPS that is greater than the camera FPS. In my case, 5FPS is sufficient for my needs .. note:: Leave Maximum FPS and Alarm Maximum FPS **empty** if you are configuring an IP camera. In older versions of ZoneMinder, you were encouraged to put a value here, but that is no longer recommended. Infact, if you see your feed going much slower than the feed is supposed to go, or you get a lot of buffering/display issues, make sure this is empty. If you need to control camera FPS, please do it directly on the camera (via its own web interface, for example) diff --git a/onvif/modules/lib/ONVIF/Client.pm b/onvif/modules/lib/ONVIF/Client.pm index 90bfdd512..9676dff85 100644 --- a/onvif/modules/lib/ONVIF/Client.pm +++ b/onvif/modules/lib/ONVIF/Client.pm @@ -76,7 +76,7 @@ my %soap_version_of :ATTR(:default<('1.1')>); sub service { my ($self, $serviceName, $attr) = @_; -#print "service: " . $services_of{${$self}}{$serviceName}{$attr} . "\n"; + #print "service: " . $services_of{${$self}}{$serviceName}{$attr} . "\n"; # Please note that the Std::Class::Fast docs say not to use ident. $services_of{ident $self}{$serviceName}{$attr}; } @@ -114,18 +114,18 @@ sub get_service_urls { my $result = $self->service('device', 'ep')->GetServices( { IncludeCapability => 'true', # boolean - },, + } ); if ( $result ) { - foreach my $svc ( @{ $result->get_Service() } ) { + foreach my $svc ( @{ $result->get_Service() } ) { my $short_name = $namespace_map{$svc->get_Namespace()}; my $url_svc = $svc->get_XAddr()->get_value(); - if(defined $short_name && defined $url_svc) { -# print "Got $short_name service\n"; + if ( defined $short_name && defined $url_svc ) { + #print "Got $short_name service\n"; $self->set_service($short_name, 'url', $url_svc); } } - # } else { + #} else { #print "No results from GetServices: $result\n"; } @@ -142,14 +142,14 @@ sub get_service_urls { if ( my $function = $capabilities->can( "get_$capability" ) ) { my $Services = $function->( $capabilities ); if ( !$Services ) { - print "Nothing returned ffrom get_$capability\n"; + #print "Nothing returned from get_$capability\n"; } else { foreach my $svc ( @{ $Services } ) { # The capability versions don't have a namespace, so just lowercase them. my $short_name = lc $capability; my $url_svc = $svc->get_XAddr()->get_value(); - if( defined $url_svc) { -# print "Got $short_name service\n"; + if ( defined $url_svc ) { + #print "Got $short_name service\n"; $self->set_service($short_name, 'url', $url_svc); } } # end foreach svr @@ -202,10 +202,10 @@ sub BUILD { # deserializer_args => { strict => 0 } }); - $services_of{$ident}{'device'} = { url => $url_svc_device, ep => $svc_device }; + $services_of{$ident}{device} = { url => $url_svc_device, ep => $svc_device }; # Can't, don't have credentials yet - #$self->get_service_urls(); + # $self->get_service_urls(); } sub get_users { @@ -260,7 +260,7 @@ sub set_credentials { sub create_services { my ($self) = @_; - #$self->get_service_urls(); + $self->get_service_urls(); if ( defined $self->service('media', 'url') ) { $self->set_service('media', 'ep', ONVIF::Media::Interfaces::Media::MediaPort->new({ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 24c5238e8..aaa412453 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -785,7 +785,7 @@ our @options = ( }, { name => 'ZM_TIMEZONE', - default => 'UTC', + default => '', description => 'The timezone that php should use.', help => q` This should be set equal to the system timezone of the mysql server`, diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm index 3fd36a0c4..59e4f7511 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm @@ -1,16 +1,6 @@ # ========================================================================== # -# ZoneMinder Amcrest HTTP API Control Protocol Module, 20180214, Rev 3.0 -# -# Change Log -# -# Rev 3.0: -# - Fixes incorrect method names -# - Updates control sequences to Amcrest HTTP Protocol API v 2.12 -# - Extends control features -# -# Rev 2.0: -# - Fixed installation instructions text, no changes to functionality. +# ZoneMinder Amcrest HTTP API Control Protocol Module # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -39,6 +29,7 @@ use Time::HiRes qw( usleep ); require ZoneMinder::Base; require ZoneMinder::Control; require LWP::UserAgent; +use URI; our @ISA = qw(ZoneMinder::Control); @@ -63,22 +54,28 @@ sub open { my $self = shift; $self->loadMonitor(); - my $username; - my $password; - my $realm = 'Login to ' . $self->{Monitor}->{ControlDevice}; - - if ( $self->{Monitor}->{ControlAddress} =~ /(.*):(.*)@(.*)/ ) { - $username = $1; - $password = $2; - $$self{address} = $3; + if ( $self->{Monitor}->{ControlAddress} !~ /^\w+:\/\// ) { + # Has no scheme at the beginning, so won't parse as a URI + $self->{Monitor}->{ControlAddress} = 'http://'.$self->{Monitor}->{ControlAddress}; } + my $uri = URI->new($self->{Monitor}->{ControlAddress}); $self->{ua} = LWP::UserAgent->new; - $self->{ua}->credentials($$self{address}, $realm, $username, $password); $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION); + my ( $username, $password ); + my $realm = 'Login to ' . $self->{Monitor}->{ControlDevice}; + if ( $self->{Monitor}->{ControlAddress} ) { + ( $username, $password ) = $uri->authority() =~ /^(.*):(.*)@(.*)$/; - # Detect REALM - my $res = $self->{ua}->get($$self{address}.'/cgi-bin/ptz.cgi'); + $$self{address} = $uri->host_port(); + $self->{ua}->credentials($uri->host_port(), $realm, $username, $password); + # Testing seems to show that we need the username/password in each url as well as credentials + $$self{base_url} = $uri->canonical(); + Debug('Using initial credentials for '.$uri->host_port().", $realm, $username, $password, base_url: $$self{base_url} auth:".$uri->authority()); + } + + # Detect REALM, has to be /cgi-bin/ptz.cgi because just / accepts no auth + my $res = $self->{ua}->get($$self{base_url}.'cgi-bin/ptz.cgi'); if ( $res->is_success ) { $self->{state} = 'open'; @@ -94,21 +91,26 @@ sub open { if ( $$headers{'www-authenticate'} ) { my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; - if ( $tokens =~ /\w+="([^"]+)"/i ) { + if ( $tokens =~ /realm="([^"]+)"/i ) { if ( $realm ne $1 ) { $realm = $1; - Debug("Changing REALM to $realm"); + Debug("Changing REALM to ($realm)"); $self->{ua}->credentials($$self{address}, $realm, $username, $password); - $res = $self->{ua}->get($$self{address}.'/cgi-bin/ptz.cgi'); + $res = $self->{ua}->get($$self{base_url}.'cgi-bin/ptz.cgi'); if ( $res->is_success() ) { $self->{state} = 'open'; return; + } elsif ( $res->status_line eq '400 Bad Request' ) { + # In testing, this second request fails with Bad Request, I assume because we didn't actually give it a command. + $self->{state} = 'open'; + return; + } else { + Error('Authentication still failed after updating REALM' . $res->status_line); + $headers = $res->headers(); + foreach my $k ( keys %$headers ) { + Debug("Header $k => $$headers{$k}"); + } # end foreach } - Error('Authentication still failed after updating REALM' . $res->status_line); - $headers = $res->headers(); - foreach my $k ( keys %$headers ) { - Debug("Initial Header $k => $$headers{$k}"); - } # end foreach } else { Error('Authentication failed, not a REALM problem'); } @@ -118,9 +120,12 @@ sub open { } else { Debug('No headers line'); } # end if headers + } else { + Error("Failed to get $$self{base_url}cgi-bin/ptz.cgi ".$res->status_line()); + } # end if $res->status_line() eq '401 Unauthorized' - $self->{state} = 'open'; + $self->{state} = 'closed'; } sub close { @@ -135,16 +140,23 @@ sub sendCmd { $self->printMsg($cmd, 'Tx'); - my $req = HTTP::Request->new( GET=>"http://$$self{address}/$cmd" ); - my $res = $self->{ua}->request($req); + my $res = $self->{ua}->get($$self{base_url}.$cmd); if ( $res->is_success ) { $result = !undef; # Command to camera appears successful, write Info item to log - Info('Camera control: \''.$res->status_line().'\' for URL '.$self->{Monitor}->{ControlAddress}.'/'.$cmd); + Info('Camera control: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); # TODO: Add code to retrieve $res->message_decode or some such. Then we could do things like check the camera status. } else { - Error('Camera control command FAILED: \''.$res->status_line().'\' for URL '.$self->{Monitor}->{ControlAddress}.'/'.$cmd); + # Try again + $res = $self->{ua}->get($$self{base_url}.$cmd); + if ( $res->is_success ) { + # Command to camera appears successful, write Info item to log + Info('Camera control 2: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); + } else { + Error('Camera control command FAILED: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); + $res = $self->{ua}->get('http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd); + } } return $result; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm new file mode 100644 index 000000000..59d9e3550 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm @@ -0,0 +1,356 @@ +# =========================================================================r +# +# ZoneMinder D-Link DCS-5020L IP Control Protocol Module, $Date: $, $Revision: $ +# Copyright (C) 2013 Art Scheel +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ========================================================================== +# +# This module contains the implementation of the D-Link DCS-5020L IP camera control +# protocol. +# +package ZoneMinder::Control::DCS5020L; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Control; + +our @ISA = qw(ZoneMinder::Control); + +our $VERSION = $ZoneMinder::Base::VERSION; + +# ========================================================================== +# +# D-Link DCS-5020L Control Protocol +# +# ========================================================================== + +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Config qw(:all); + +use Time::HiRes qw( usleep ); + +sub new +{ + my $class = shift; + my $id = shift; + my $self = ZoneMinder::Control->new( $id ); + bless( $self, $class ); + srand( time() ); + return $self; +} + +our $AUTOLOAD; + +sub AUTOLOAD +{ + my $self = shift; + my $class = ref($self) || croak( "$self not object" ); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( exists($self->{$name}) ) + { + return( $self->{$name} ); + } + Fatal( "Can't access $name member of object of class $class" ); +} + +sub open +{ + my $self = shift; + + $self->loadMonitor(); + + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent( "ZoneMinder Control Agent/" . ZoneMinder::Base::ZM_VERSION ); + $self->{state} = 'open'; +} + +sub close +{ + my $self = shift; + $self->{state} = 'closed'; +} + +sub printMsg +{ + my $self = shift; + my $msg = shift; + my $msg_len = length($msg); + + Debug( $msg."[".$msg_len."]" ); +} + +sub sendCmd +{ + my $self = shift; + my $cmd = shift; + my $cgi = shift; + + my $result = undef; + + printMsg( $cmd, "Tx" ); + + my $req = HTTP::Request->new( POST=>"http://$self->{Monitor}->{ControlAddress}/$cgi.cgi" ); + $req->content($cmd); + my $res = $self->{ua}->request($req); + + if ( $res->is_success ) + { + $result = !undef; + } + else + { + Error( "Error check failed: '".$res->status_line()."'" ); + } + + return( $result ); +} + +sub move +{ + my $self = shift; + my $dir = shift; + my $panStep = shift; + my $tiltStep = shift; + my $cmd = "PanSingleMoveDegree=$panStep&TiltSingleMoveDegree=$tiltStep&PanTiltSingleMove=$dir"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub moveRel +{ + my $self = shift; + my $params = shift; + my $panStep = $self->getParam($params, 'panstep', 0); + my $tiltStep = $self->getParam($params, 'tiltstep', 0); + my $dir = shift; + $self->move( $dir, $panStep, $tiltStep ); +} + +sub moveRelUpLeft +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 0 ); +} + +sub moveRelUp +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 1 ); +} + +sub moveRelUpRight +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 2 ); +} + +sub moveRelLeft +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 3 ); +} + +sub moveRelRight +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 5 ); +} + +sub moveRelDownLeft +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 6 ); +} + +sub moveRelDown +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 7 ); +} + +sub moveRelDownRight +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 8 ); +} + +# moves the camera to center on the point that the user clicked on in the video image. +# This isn't extremely accurate but good enough for most purposes +sub moveMap +{ + # if the camera moves too much or too little, try increasing or decreasing this value + my $f = 11; + + my $self = shift; + my $params = shift; + my $xcoord = $self->getParam( $params, 'xcoord' ); + my $ycoord = $self->getParam( $params, 'ycoord' ); + + my $hor = $xcoord * 100 / $self->{Monitor}->{Width}; + my $ver = $ycoord * 100 / $self->{Monitor}->{Height}; + + my $direction; + my $horSteps; + my $verSteps; + if ($hor < 50 && $ver < 50) { + # up left + $horSteps = (50 - $hor) / $f; + $verSteps = (50 - $ver) / $f; + $direction = 0; + } elsif ($hor >= 50 && $ver < 50) { + # up right + $horSteps = ($hor - 50) / $f; + $verSteps = (50 - $ver) / $f; + $direction = 2; + } elsif ($hor < 50 && $ver >= 50) { + # down left + $horSteps = (50 - $hor) / $f; + $verSteps = ($ver - 50) / $f; + $direction = 6; + } elsif ($hor >= 50 && $ver >= 50) { + # down right + $horSteps = ($hor - 50) / $f; + $verSteps = ($ver - 50) / $f; + $direction = 8; + } + my $v = int($verSteps + .5); + my $h = int($horSteps + .5); + Debug( "Move Map to $xcoord,$ycoord, hor=$h, ver=$v with direction $direction" ); + $self->move( $direction, $h, $v ); +} + +sub presetClear +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Clear Preset $preset" ); + my $cmd = "ClearPosition=$preset"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub presetSet +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Set Preset $preset" ); + my $cmd = "SetCurrentPosition=$preset&SetName=preset_$preset"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub presetGoto +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Goto Preset $preset" ); + my $cmd = "PanTiltPresetPositionMove=$preset"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub presetHome +{ + my $self = shift; + Debug( "Home Preset" ); + $self->move( 4, 0, 0 ); +} + + +# IR Controls +# +# wake = IR on +# sleep = IR off +# reset = IR auto + +sub setDayNightMode { + my $self = shift; + my $mode = shift; + my $cmd = "DayNightMode=$mode&ConfigReboot=No"; + $self->sendCmd( $cmd, 'daynight' ); +} + +sub wake +{ + my $self = shift; + Debug( "Wake - IR on" ); + $self->setDayNightMode(2); +} + +sub sleep +{ + my $self = shift; + Debug( "Sleep - IR off" ); + $self->setDayNightMode(3); +} + +sub reset +{ + my $self = shift; + Debug( "Reset - IR auto" ); + $self->setDayNightMode(0); +} + +1; +__END__ +# Below is stub documentation for your module. You'd better edit it! + +=head1 NAME + +ZoneMinder::Database - Perl extension for DCS-5020L + +=head1 SYNOPSIS + + use ZoneMinder::Database; + DLINK DCS-5020L + +=head1 DESCRIPTION + +ZoneMinder driver for the D-Link consumer camera DCS-5020L. + +=head2 EXPORT + +None by default. + + + +=head1 SEE ALSO + +See if there are better instructions for the DCS-5020L at +http://www.zoneminder.com/wiki/index.php/Dlink + +=head1 AUTHOR + +Art Scheel ascheel (at) gmail + +=head1 COPYRIGHT AND LICENSE + +LGPLv3 + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm index 8754500fa..a9f8d8f1b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm @@ -95,9 +95,9 @@ sub PutCmd { my $self = shift; my $cmd = shift; my $content = shift; - my $req = HTTP::Request->new(PUT => "$self->{BaseURL}/$cmd"); + my $req = HTTP::Request->new(PUT => $self->{BaseURL}.'/'.$cmd); if ( defined($content) ) { - $req->content_type("application/x-www-form-urlencoded; charset=UTF-8"); + $req->content_type('application/x-www-form-urlencoded; charset=UTF-8'); $req->content('' . "\n" . $content); } my $res = $self->{UA}->request($req); @@ -135,13 +135,13 @@ sub PutCmd { # Check for username/password # if ( $self->{Monitor}{ControlAddress} =~ /.+:(.+)@.+/ ) { - Info("Check username/password is correct"); + Info('Check username/password is correct'); } elsif ( $self->{Monitor}{ControlAddress} =~ /^[^:]+@.+/ ) { - Info("No password in Control Address. Should there be one?"); + Info('No password in Control Address. Should there be one?'); } elsif ( $self->{Monitor}{ControlAddress} =~ /^:.+@.+/ ) { - Info("Password but no username in Control Address."); + Info('Password but no username in Control Address.'); } else { - Info("Missing username and password in Control Address."); + Info('Missing username and password in Control Address.'); } Fatal($res->status_line); } @@ -382,7 +382,7 @@ sub irisRelOpen { sub reset { my $self = shift; - $self->PutCmd("ISAPI/System/reboot"); + $self->PutCmd('ISAPI/System/reboot'); } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in index f6863367d..f4dedb3b9 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in @@ -255,15 +255,15 @@ sub discover { sub profiles { my ( $client ) = @_; - my $endpoint = $client->get_endpoint('media'); - if ( ! $endpoint ) { - print "No media enpoint for client.\n"; + my $media = $client->get_endpoint('media'); + if ( ! $media ) { + print "No media endpoint for client.\n"; return; } - my $result = $endpoint->GetProfiles( { } ,, ); + my $result = $media->GetProfiles( { } ,, ); if ( ! $result ) { - print "No result from GetProfiles\n"; + print "No result from GetProfiles.\n"; return; } if ( $verbose ) { @@ -272,48 +272,52 @@ sub profiles { my $profiles = $result->get_Profiles(); - foreach my $profile ( @{ $profiles } ) { + foreach my $profile ( @{ $profiles } ) { my $token = $profile->attr()->get_token() ; - my $video_encoder_configuration = $profile->get_VideoEncoderConfiguration(); - if ( ! $video_encoder_configuration ) { - print "Unknown profile $token " . $profile->get_Name()."\n"; + my $Name = $profile->get_Name(); + + my $VideoEncoderConfiguration = $profile->get_VideoEncoderConfiguration(); + if ( ! $VideoEncoderConfiguration ) { + print "Unknown profile $token $Name.\n"; next; } - print $token . ", " . - $profile->get_Name() . ", " . - $profile->get_VideoEncoderConfiguration()->get_Encoding() . ", " . - $profile->get_VideoEncoderConfiguration()->get_Resolution()->get_Width() . ", " . - $profile->get_VideoEncoderConfiguration()->get_Resolution()->get_Height() . ", " . - $profile->get_VideoEncoderConfiguration()->get_RateControl()->get_FrameRateLimit() . - ", "; # Specification gives conflicting values for unicast stream types, try both. # http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl#op.GetStreamUri - foreach my $streamtype ( 'RTP_unicast', 'RTP-Unicast' ) { - $result = $client->get_endpoint('media')->GetStreamUri( { + foreach my $streamtype ( 'RTP_unicast', 'RTP-Unicast', 'RTP-multicast', 'RTP-Multicast' ) { + my $StreamUri = $media->GetStreamUri( { StreamSetup => { # ONVIF::Media::Types::StreamSetup - Stream => $streamtype, # StreamType - Transport => { # ONVIF::Media::Types::Transport - Protocol => 'RTSP', # TransportProtocol - }, + Stream => $streamtype, # StreamType + Transport => { # ONVIF::Media::Types::Transport + Protocol => 'RTSP', # TransportProtocol + }, }, ProfileToken => $token, # ReferenceToken - } ,, ); - last if $result; - } - die $result if not $result; -# print $result . "\n"; + } ); + next if ! ( $StreamUri and $StreamUri->can('get_MediaUri') ); + my $MediaUri = $StreamUri->get_MediaUri(); + next if ! $MediaUri; + my $Uri = $MediaUri->get_Uri(); + next if ! $Uri; + + print join(', ', $token, + $Name, + $VideoEncoderConfiguration->get_Encoding(), + $VideoEncoderConfiguration->get_Resolution()->get_Width(), + $VideoEncoderConfiguration->get_Resolution()->get_Height(), + $VideoEncoderConfiguration->get_RateControl()->get_FrameRateLimit(), + $Uri, + ) . "\n"; + } # end foreach streamtype - print $result->get_MediaUri()->get_Uri() . - "\n"; } # end foreach profile # # use message parser without schema validation ??? # -} +} # end sub profiles sub move { my ($client, $dir) = @_; @@ -326,13 +330,22 @@ sub move { sub metadata { my ( $client ) = @_; - my $result = $client->get_endpoint('media')->GetMetadataConfigurations( { } ,, ); - die $result if not $result; - print $result . "\n"; + my $media = $client->get_endpoint('media'); + die 'No media endpoint.' if !$media; - $result = $client->get_endpoint('media')->GetVideoAnalyticsConfigurations( { } ,, ); - die $result if not $result; - print $result . "\n"; + my $result = $media->GetMetadataConfigurations( { } ,, ); + if ( ! $result ) { + print "No MetaDataConfigurations\n" if $verbose; + } else { + print $result . "\n"; + } + + $result = $media->GetVideoAnalyticsConfigurations( { } ,, ); + if ( ! $result ) { + print "No VideoAnalyticsConfigurations\n" if $verbose; + } else { + print $result . "\n"; + } # $result = $client->get_endpoint('analytics')->GetServiceCapabilities( { } ,, ); # die $result if not $result; diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 9204ef3a1..e57d98add 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -420,15 +420,15 @@ MAIN: while( $loop ) { Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir"); { my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"); - Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . ' entries.' ); + Debug('glob("'.$monitor_dir.'/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned '.(scalar @event_dirs).' entries.'); foreach my $event_dir ( @event_dirs ) { if ( ! -d $event_dir ) { - Debug( "$event_dir is not a dir. Skipping" ); + Debug("$event_dir is not a dir. Skipping"); next; } my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/; - if ( ! $event_id ) { - Debug("Unable to parse date/event_id from $event_dir"); + if ( !$event_id ) { + Debug('Unable to parse date/event_id from '.$event_dir); next; } my $Event = $fs_events->{$event_id} = new ZoneMinder::Event(); @@ -438,8 +438,9 @@ MAIN: while( $loop ) { $Event->MonitorId( $monitor_dir ); $Event->StorageId( $Storage->Id() ); $Event->Path(); + $Event->age(); Debug("Have event $$Event{Id} at $$Event{Path}"); - $Event->StartTime( POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($Event->Path())) ) ); + $Event->StartTime(POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($Event->Path())))); } # end foreach event } @@ -466,13 +467,13 @@ MAIN: while( $loop ) { } # end foreach event chdir( $Storage->Path() ); } # if USE_DEEP_STORAGE - Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir" ); + Debug('Got '.int(keys(%$fs_events)).' filesystem events for monitor '.$monitor_dir); delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir); } # end foreach monitor if ( $cleaned ) { - Debug("First stage cleaning done. Restarting."); + Debug('First stage cleaning done. Restarting.'); redo MAIN; } @@ -484,7 +485,7 @@ MAIN: while( $loop ) { next; } my @event_ids = keys %$fs_events; - Debug('Have ' .scalar @event_ids . " events for monitor $monitor_id"); + Debug('Have ' .scalar @event_ids . ' events for monitor '.$monitor_id); foreach my $fs_event_id ( sort { $a <=> $b } keys %$fs_events ) { @@ -499,8 +500,8 @@ MAIN: while( $loop ) { } my $age = $Event->age(); - if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { - aud_print( "Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old" ); + if ( $age and ($age > $Config{ZM_AUDIT_MIN_AGE}) ) { + aud_print("Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old"); if ( confirm() ) { $Event->delete_files(); $cleaned = 1; @@ -586,7 +587,7 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) { } else { Debug("$$Event{Id} Not found at $path"); } - } + } # end foreach Storage if ( $Event->Archived() ) { Warning("Event $$Event{Id} is Archived. Taking no further action on it."); next; @@ -638,18 +639,13 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) { Info("Updating storage area on event $$Event{Id} from $$Event{StorageId} to $$fs_events{$db_event}{StorageId}"); $Event->StorageId($$fs_events{$db_event}->StorageId()); } - if ( $$fs_events{$db_event}->StartTime() ne $Event->StartTime() ) { + if ( ! $Event->StartTime() ) { Info("Updating StartTime on event $$Event{Id} from $$Event{StartTime} to $$fs_events{$db_event}{StartTime}"); - if ( $$Event{Scheme} eq 'Deep' ) { - $Event->StartTime($$fs_events{$db_event}->StartTime()); - } else { - $Event->StartTime($$fs_events{$db_event}->StartTime()); - } - $Event->save(); + $Event->StartTime($$fs_events{$db_event}->StartTime()); } $Event->save(); - } + } # end if Event exists in db and not in filesystem } # end if ! in fs_events } # foreach db_event } # end foreach db_monitor diff --git a/scripts/zmcamtool.pl.in b/scripts/zmcamtool.pl.in index 603c979bb..7e345b79d 100644 --- a/scripts/zmcamtool.pl.in +++ b/scripts/zmcamtool.pl.in @@ -351,8 +351,10 @@ sub exportsql { } } - if ($ARGV[0]) { - $command .= qq( --where="Name = '$ARGV[0]'"); + my $name = $ARGV[0]; + if ($name && $name =~ /^([A-Za-z0-9 ,.&()\/\-]+)$/) { # Allow alphanumeric and " ,.&()/-" + $name = $1; + $command .= qq( --where="Name = '$name'"); } $command .= " zm Controls MonitorPresets"; diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 2ca12972d..36ea2b391 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -117,7 +117,7 @@ bool EventStream::loadEventData(uint64_t event_id) { snprintf(sql, sizeof(sql), "SELECT `MonitorId`, `StorageId`, `Frames`, unix_timestamp( `StartTime` ) AS StartTimestamp, " "(SELECT max(`Delta`)-min(`Delta`) FROM `Frames` WHERE `EventId`=`Events`.`Id`) AS Duration, " - "`DefaultVideo`, `Scheme`, `SaveJPEGs` FROM `Events` WHERE `Id` = %" PRIu64, event_id); + "`DefaultVideo`, `Scheme`, `SaveJPEGs`, `Orientation`+0 FROM `Events` WHERE `Id` = %" PRIu64, event_id); if ( mysql_query(&dbconn, sql) ) { Error("Can't run query: %s", mysql_error(&dbconn)); @@ -160,6 +160,7 @@ bool EventStream::loadEventData(uint64_t event_id) { event_data->scheme = Storage::SHALLOW; } event_data->SaveJPEGs = dbrow[7] == NULL ? 0 : atoi(dbrow[7]); + event_data->Orientation = (Monitor::Orientation)(dbrow[8] == NULL ? 0 : atoi(dbrow[8])); mysql_free_result(result); Storage * storage = new Storage(event_data->storage_id); @@ -703,6 +704,30 @@ Debug(1, "Loading image"); Error("Failed getting a frame."); return false; } + + // when stored as an mp4, we just have the rotation as a flag in the headers + // so we need to rotate it before outputting + if ( event_data->Orientation != Monitor::ROTATE_0 ) { + Debug(2, "Rotating image %d", event_data->Orientation); + switch ( event_data->Orientation ) { + case Monitor::ROTATE_0 : + // No action required + break; + case Monitor::ROTATE_90 : + case Monitor::ROTATE_180 : + case Monitor::ROTATE_270 : + image->Rotate((event_data->Orientation-1)*90); + break; + case Monitor::FLIP_HORI : + case Monitor::FLIP_VERT : + image->Flip(event_data->Orientation==Monitor::FLIP_HORI); + break; + default: + Error("Invalid Orientation: %d", event_data->Orientation); + } + } else { + Debug(2, "Not Rotating image %d", event_data->Orientation); + } // end if have rotation } else { Error("Unable to get a frame"); return false; diff --git a/src/zm_eventstream.h b/src/zm_eventstream.h index 5e7d91bb2..d0b5827e7 100644 --- a/src/zm_eventstream.h +++ b/src/zm_eventstream.h @@ -66,6 +66,7 @@ class EventStream : public StreamBase { char video_file[PATH_MAX]; Storage::Schemes scheme; int SaveJPEGs; + Monitor::Orientation Orientation; }; protected: diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index b60996a78..539205e3b 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -128,14 +128,14 @@ else fi; fi +IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE" if [ "$PPA" == "" ]; then if [ "$RELEASE" != "" ]; then # We need to use our official tarball for the original source, so grab it and overwrite our generated one. - IFS='.' read -r -a VERSION <<< "$RELEASE" - if [ "${VERSION[0]}.${VERSION[1]}" == "1.30" ]; then + if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then PPA="ppa:iconnor/zoneminder-stable" else - PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}" + PPA="ppa:iconnor/zoneminder-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" fi; else if [ "$BRANCH" == "" ]; then @@ -316,7 +316,7 @@ EOF read -p "Do you want to upload this binary to zmrepo? (y/N)" if [[ $REPLY == [yY] ]]; then if [ "$RELEASE" != "" ]; then - scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/stable/mini-dinstall/incoming/" + scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/release-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}/mini-dinstall/incoming/" else if [ "$BRANCH" == "" ]; then scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/master/mini-dinstall/incoming/" diff --git a/utils/packpack/rsync_xfer.sh b/utils/packpack/rsync_xfer.sh index f6ce377e7..c9a737a03 100755 --- a/utils/packpack/rsync_xfer.sh +++ b/utils/packpack/rsync_xfer.sh @@ -18,7 +18,16 @@ for CMD in sshfs rsync find fusermount mkdir; do done if [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbian" ]; then - targetfolder="debian/master/mini-dinstall/incoming" + if [ "${RELEASE}" != "" ]; then + IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE" + if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then + targetfolder="debian/release/mini-dinstall/incoming" + else + targetfolder="debian/release-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}/mini-dinstall/incoming" + fi + else + targetfolder="debian/master/mini-dinstall/incoming" + fi else targetfolder="travis" fi diff --git a/version b/version index 2b17ffd50..7e3856fe8 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.34.0 +1.34.3 diff --git a/web/ajax/add_monitors.php b/web/ajax/add_monitors.php index de24cdfac..e7048bb4a 100644 --- a/web/ajax/add_monitors.php +++ b/web/ajax/add_monitors.php @@ -46,18 +46,18 @@ if ( 0 ) { SOL_SOCKET, // socket level SO_SNDTIMEO, // timeout option array( - "sec"=>0, // Timeout in seconds - "usec"=>500 // I assume timeout in microseconds + 'sec'=>0, // Timeout in seconds + 'usec'=>500 // I assume timeout in microseconds ) ); $new_stream = null; -Info("Testing connection to " . $url_bits['host'].':'.$port); + Info('Testing connection to '.$url_bits['host'].':'.$port); if ( socket_connect( $socket, $url_bits['host'], $port ) ) { $new_stream = $url_bits; // make a copy $new_stream['port'] = $port; } else { socket_close($socket); - ZM\Info("No connection to ".$url_bits['host'] . " on port $port"); + ZM\Info('No connection to '.$url_bits['host'].' on port '.$port); continue; } if ( $new_stream ) { @@ -92,10 +92,10 @@ Info("Testing connection to " . $url_bits['host'].':'.$port); } foreach ( $available_streams as &$stream ) { # check for existence in db. - $stream['url'] = unparse_url( $stream, array('path'=>'/','query'=>'action=stream') ); - $monitors = ZM\Monitor::find( array('Path'=>$stream['url']) ); + $stream['url'] = unparse_url($stream, array('path'=>'/','query'=>'action=stream')); + $monitors = ZM\Monitor::find(array('Path'=>$stream['url'])); if ( count($monitors) ) { - ZM\Info("Found monitors matching " . $stream['url'] ); + ZM\Info('Found monitors matching ' . $stream['url'] ); $stream['Monitor'] = $monitors[0]; if ( isset( $stream['Width'] ) and ( $stream['Monitor']->Width() != $stream['Width'] ) ) { $stream['Warning'] .= 'Monitor width ('.$stream['Monitor']->Width().') and stream width ('.$stream['Width'].") do not match!\n"; @@ -106,11 +106,11 @@ Info("Testing connection to " . $url_bits['host'].':'.$port); } else { $stream['Monitor'] = clone $defaultMonitor; if ( isset($stream['Width']) ) { - $stream['Monitor']->Width( $stream['Width'] ); - $stream['Monitor']->Height( $stream['Height'] ); + $stream['Monitor']->Width($stream['Width']); + $stream['Monitor']->Height($stream['Height']); } if ( isset($stream['Name']) ) { - $stream['Monitor']->Name( $stream['Name'] ); + $stream['Monitor']->Name($stream['Name']); } } // Monitor found or not } // end foreach Stream @@ -121,16 +121,16 @@ Info("Testing connection to " . $url_bits['host'].':'.$port); return $available_streams; } // end function probe -if ( canEdit( 'Monitors' ) ) { +if ( canEdit('Monitors') ) { switch ( $_REQUEST['action'] ) { case 'probe' : { $available_streams = array(); $url_bits = null; - if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $_REQUEST['url'] ) ) { - $url_bits = array( 'host'=>$_REQUEST['url'] ); + if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $_REQUEST['url']) ) { + $url_bits = array('host'=>$_REQUEST['url']); } else { - $url_bits = parse_url( $_REQUEST['url'] ); + $url_bits = parse_url($_REQUEST['url']); } if ( 0 ) { @@ -147,13 +147,13 @@ if ( 0 ) { } if ( ! $url_bits ) { - ajaxError("The given URL was too malformed to parse."); + ajaxError('The given URL was too malformed to parse.'); return; } - $available_streams = probe( $url_bits ); + $available_streams = probe($url_bits); - ajaxResponse( array('Streams'=>$available_streams) ); + ajaxResponse(array('Streams'=>$available_streams)); return; } // end case url_probe case 'import': @@ -161,16 +161,16 @@ if ( 0 ) { $file = $_FILES['import_file']; - if ($file["error"] > 0) { - ajaxError($file["error"]); + if ( $file['error'] > 0 ) { + ajaxError($file['error']); return; } else { - $filename = $file["name"]; + $filename = $file['name']; - $available_streams = array(); + $available_streams = array(); $row = 1; - if (($handle = fopen($file['tmp_name'], 'r')) !== FALSE) { - while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) { + if ( ($handle = fopen($file['tmp_name'], 'r')) !== FALSE ) { + while ( ($data = fgetcsv($handle, 1000, ',')) !== FALSE ) { $name = $data[0]; $url = $data[1]; $group = $data[2]; @@ -178,16 +178,16 @@ if ( 0 ) { $url_bits = null; if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $url) ) { - $url_bits = array( 'host'=>$url, 'scheme'=>'http' ); + $url_bits = array('host'=>$url, 'scheme'=>'http'); } else { - $url_bits = parse_url( $url ); + $url_bits = parse_url($url); } if ( ! $url_bits ) { ZM\Info("Bad url, skipping line $name $url $group"); continue; } - $available_streams += probe( $url_bits ); + $available_streams += probe($url_bits); //$url_bits['url'] = unparse_url( $url_bits ); //$url_bits['Monitor'] = $defaultMonitor; @@ -197,23 +197,19 @@ if ( 0 ) { } // end while rows fclose($handle); - ajaxResponse( array('Streams'=>$available_streams) ); + ajaxResponse(array('Streams'=>$available_streams)); } else { - ajaxError("Uploaded file does not exist"); + ajaxError('Uploaded file does not exist'); return; } - } } // end case import default: - { - ZM\Warning("unknown action " . $_REQUEST['action'] ); - } // end ddcase default - } + ZM\Warning('unknown action '.$_REQUEST['action']); + } // end switch action } else { - ZM\Warning("Cannot edit monitors" ); + ZM\Warning('Cannot edit monitors'); } -ajaxError( 'Unrecognised action or insufficient permissions' ); - +ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user ' . $user['Username']); ?> diff --git a/web/ajax/console.php b/web/ajax/console.php index 1a5919b58..ae8a60b15 100644 --- a/web/ajax/console.php +++ b/web/ajax/console.php @@ -1,35 +1,33 @@ beginTransaction(); - $dbConn->exec('LOCK TABLES Monitors WRITE'); - for ( $i = 0; $i < count($monitor_ids); $i += 1 ) { - $monitor_id = $monitor_ids[$i]; - $monitor_id = preg_replace( '/^monitor_id-/', '', $monitor_id ); - if ( ( ! $monitor_id ) or ! ( is_integer( $monitor_id ) or ctype_digit( $monitor_id ) ) ) { - Warning("Got $monitor_id from " . $monitor_ids[$i]); - continue; - } - dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($i, $monitor_id)); - } // end for each monitor_id - $dbConn->commit(); - $dbConn->exec('UNLOCK TABLES'); - - return; - } // end case sort - default: - { - ZM\Warning('unknown action ' . $_REQUEST['action']); - } // end ddcase default - } + switch ( $_REQUEST['action'] ) { + case 'sort' : + { + $monitor_ids = $_POST['monitor_ids']; + # Two concurrent sorts could generate odd sortings... so lock the table. + global $dbConn; + $dbConn->beginTransaction(); + $dbConn->exec('LOCK TABLES Monitors WRITE'); + for ( $i = 0; $i < count($monitor_ids); $i += 1 ) { + $monitor_id = $monitor_ids[$i]; + $monitor_id = preg_replace('/^monitor_id-/', '', $monitor_id); + if ( ( !$monitor_id ) or ! ( is_integer($monitor_id) or ctype_digit($monitor_id) ) ) { + Warning('Got '.$monitor_id.' from '.$monitor_ids[$i]); + continue; + } + dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($i, $monitor_id)); + } // end for each monitor_id + $dbConn->commit(); + $dbConn->exec('UNLOCK TABLES'); + + return; + } // end case sort + default: + ZM\Warning('unknown action '.$_REQUEST['action']); + } } else { ZM\Warning('Cannot edit monitors'); } -ajaxError('Unrecognised action or insufficient permissions'); +ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user ' . $user['Username']); ?> diff --git a/web/ajax/event.php b/web/ajax/event.php index cb4d3d7ad..9c2e57c2c 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -155,5 +155,5 @@ if ( canEdit('Events') ) { } // end switch action } // end if canEdit('Events') -ajaxError('Unrecognised action or insufficient permissions'); +ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user ' . $user['Username']); ?> diff --git a/web/includes/actions/login.php b/web/includes/actions/login.php index 6c8312b2f..68ca4e604 100644 --- a/web/includes/actions/login.php +++ b/web/includes/actions/login.php @@ -49,14 +49,23 @@ if ( ('login' == $action) && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == ' // as it produces the same error as when you don't answer a recaptcha if ( isset($responseData['error-codes']) && is_array($responseData['error-codes']) ) { if ( !in_array('invalid-input-secret', $responseData['error-codes']) ) { - Error('reCaptcha authentication failed'); + ZM\Error('reCaptcha authentication failed. response was: ' . print_r($responseData['error-codes'],true)); unset($user); // unset should be ok here because we aren't in a function return; } else { - Error('Invalid recaptcha secret detected'); + ZM\Error('Invalid recaptcha secret detected'); } } } // end if success==false + if ( ! (empty($_REQUEST['username']) or empty($_REQUEST['password'])) ) { + $ret = validateUser($_REQUEST['username'], $_REQUEST['password']); + if ( !$ret[0] ) { + ZM\Error($ret[1]); + unset($user); // unset should be ok here because we aren't in a function + } else { + $user = $ret[0]; + } + } # end if have username and password } // end if using reCaptcha // if captcha existed, it was passed diff --git a/web/includes/config.php.in b/web/includes/config.php.in index 909a10e15..57064f22d 100644 --- a/web/includes/config.php.in +++ b/web/includes/config.php.in @@ -193,7 +193,8 @@ if ( ! defined('ZM_SERVER_ID') ) { } } -ini_set('date.timezone', ZM_TIMEZONE); +if ( ZM_TIMEZONE ) + ini_set('date.timezone', ZM_TIMEZONE); function process_configfile($configFile) { if ( is_readable( $configFile ) ) { diff --git a/web/index.php b/web/index.php index c912075fd..63aa8c672 100644 --- a/web/index.php +++ b/web/index.php @@ -202,7 +202,8 @@ isset($action) || $action = NULL; if ( (!$view and !$request) or ($view == 'console') ) { // Verify the system, php, and mysql timezones all match - date_default_timezone_set(ZM_TIMEZONE); + #if ( ZM_TIMEZONE ) + #date_default_timezone_set(ZM_TIMEZONE); check_timezone(); } @@ -245,7 +246,6 @@ if ( ZM_OPT_USE_AUTH and (!isset($user)) and ($view != 'login') and ($view != 'n if ( ! $request ) { zm_session_start(); $_SESSION['postLoginQuery'] = $_SERVER['QUERY_STRING']; - ZM\Error("postLoginQuery " . $_SESSION['postLoginQuery']); session_write_close(); } $request = null; diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 4c43acba9..0570005d3 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -775,6 +775,7 @@ $SLANG = array( 'TurboPanSpeed' => 'Turbo Pan Speed', 'TurboTiltSpeed' => 'Turbo Tilt Speed', 'Type' => 'Type', + 'TZUnset' => 'Unset - use value in php.ini', 'Unarchive' => 'Unarchive', 'Undefined' => 'Undefined', 'Units' => 'Units', diff --git a/web/skins/classic/css/base/views/options.css b/web/skins/classic/css/base/views/options.css index f7123086c..a60d98001 100644 --- a/web/skins/classic/css/base/views/options.css +++ b/web/skins/classic/css/base/views/options.css @@ -21,3 +21,8 @@ input.large { #contentTable.userTable .colMonitor, #contentTable.userTable .colUsername { text-align: left; } + +input[name="newConfig[ZM_OPT_GOOG_RECAPTCHA_SITEKEY]"], +input[name="newConfig[ZM_OPT_GOOG_RECAPTCHA_SECRETKEY]"] { + width: 100%; +} diff --git a/web/skins/classic/includes/control_functions.php b/web/skins/classic/includes/control_functions.php index 7554486af..80cfe4aff 100644 --- a/web/skins/classic/includes/control_functions.php +++ b/web/skins/classic/includes/control_functions.php @@ -125,7 +125,7 @@ function controlPanTilt($monitor, $cmds) { - + diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index 9f14d93d0..ceaee50d5 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -423,7 +423,14 @@ if ( (!ZM_OPT_USE_AUTH) or $user ) { $storage_areas = $storage_areas_with_no_server_id; if ( count($storage_areas) <= 4 ) echo implode(', ', array_map($func, $storage_areas)); - echo ' ' . ZM_PATH_MAP .': '. getDiskPercent(ZM_PATH_MAP).'%'; + $shm_percent = getDiskPercent(ZM_PATH_MAP); + $class = ''; + if ( $shm_percent > 98 ) { + $class = 'error'; + } else if ( $shm_percent > 90 ) { + $class = 'warning'; + } + echo ' '.ZM_PATH_MAP.': '.$shm_percent.'%'; ?> diff --git a/web/skins/classic/views/js/filter.js b/web/skins/classic/views/js/filter.js index dd2c8e8c0..68fee11b5 100644 --- a/web/skins/classic/views/js/filter.js +++ b/web/skins/classic/views/js/filter.js @@ -169,18 +169,18 @@ function parseRows(rows) { } var brackets = rows.length - 2; - if ( brackets > 0 ) { //add bracket select to all rows + if ( brackets > 0 ) { // add bracket select to all rows var obrSelect = $j('').attr('name', queryPrefix + rowNum + '][obr]').attr('id', queryPrefix + rowNum + '][obr]'); var cbrSelect = $j('').attr('name', queryPrefix + rowNum + '][cbr]').attr('id', queryPrefix + rowNum + '][cbr]'); obrSelect.append(''); cbrSelect.append(''); } - var obrVal = inputTds.eq(1).children().val() != undefined ? inputTds.eq(1).children().val() : 0; //Save currently selected bracket option + var obrVal = inputTds.eq(1).children().val() != undefined ? inputTds.eq(1).children().val() : 0; // Save currently selected bracket option var cbrVal = inputTds.eq(5).children().val() != undefined ? inputTds.eq(5).children().val() : 0; - inputTds.eq(1).html(obrSelect).children().val(obrVal); //Set bracket contents and assign saved value + inputTds.eq(1).html(obrSelect).children().val(obrVal); // Set bracket contents and assign saved value inputTds.eq(5).html(cbrSelect).children().val(cbrVal); } else { inputTds.eq(1).html(' '); // Blank if there aren't enough terms for brackets @@ -188,7 +188,7 @@ function parseRows(rows) { } if ( rows.length == 1 ) { - inputTds.eq(6).find('button[data-on-click-this="delTerm"]').prop('disabled', true); //enable/disable remove row button + inputTds.eq(6).find('button[data-on-click-this="delTerm"]').prop('disabled', true); // enable/disable remove row button } else { inputTds.eq(6).find('button[data-on-click-this="delTerm"]').prop('disabled', false); } @@ -304,11 +304,16 @@ function addTerm( element ) { this[0].selected = 'selected'; }).chosen({width: '101%'}); newRow.find('input[type="text"]').val(''); - newRow[0].querySelectorAll("button[data-on-click-this]").forEach(function attachOnClick(el) { + newRow[0].querySelectorAll("button[data-on-click-this]").forEach(function(el) { var fnName = el.getAttribute("data-on-click-this"); el.onclick = window[fnName].bind(el, el); }); + newRow[0].querySelectorAll('select[data-on-change-this]').forEach(function(el) { + var fnName = el.getAttribute('data-on-change-this'); + el.onchange = window[fnName].bind(el, el); + }); + var rows = $j(row).parent().children(); parseRows(rows); } diff --git a/web/skins/classic/views/options.php b/web/skins/classic/views/options.php index 82692fbaf..e1a77b874 100644 --- a/web/skins/classic/views/options.php +++ b/web/skins/classic/views/options.php @@ -393,41 +393,41 @@ foreach ( array_map('basename', glob('skins/'.$skin.'/css/*',GLOB_ONLYDIR)) as $ $configCats[$tab]['ZM_SKIN_DEFAULT']['Hint'] = join('|', array_map('basename', glob('skins/*',GLOB_ONLYDIR))); $configCats[$tab]['ZM_CSS_DEFAULT']['Hint'] = join('|', array_map ( 'basename', glob('skins/'.ZM_SKIN_DEFAULT.'/css/*',GLOB_ONLYDIR) )); $configCats[$tab]['ZM_BANDWIDTH_DEFAULT']['Hint'] = $bandwidth_options; + function timezone_list() { - static $timezones = null; + static $timezones = null; - if ($timezones === null) { - $timezones = []; - $offsets = []; - $now = new DateTime('now', new DateTimeZone('UTC')); + if ( $timezones === null ) { + $timezones = []; + $offsets = []; + $now = new DateTime('now', new DateTimeZone('UTC')); - foreach (DateTimeZone::listIdentifiers() as $timezone) { - $now->setTimezone(new DateTimeZone($timezone)); - $offsets[] = $offset = $now->getOffset(); - $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone); + foreach ( DateTimeZone::listIdentifiers() as $timezone ) { + $now->setTimezone(new DateTimeZone($timezone)); + $offsets[] = $offset = $now->getOffset(); + $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone); + } + + array_multisort($offsets, $timezones); + } + + return $timezones; } - array_multisort($offsets, $timezones); - } + function format_GMT_offset($offset) { + $hours = intval($offset / 3600); + $minutes = abs(intval($offset % 3600 / 60)); + return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : ''); + } - return $timezones; -} - -function format_GMT_offset($offset) { - $hours = intval($offset / 3600); - $minutes = abs(intval($offset % 3600 / 60)); - return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : ''); -} - -function format_timezone_name($name) { - $name = str_replace('/', ', ', $name); - $name = str_replace('_', ' ', $name); - $name = str_replace('St ', 'St. ', $name); - return $name; -} - $configCats[$tab]['ZM_TIMEZONE']['Hint'] = timezone_list(); - - } + function format_timezone_name($name) { + $name = str_replace('/', ', ', $name); + $name = str_replace('_', ' ', $name); + $name = str_replace('St ', 'St. ', $name); + return $name; + } + $configCats[$tab]['ZM_TIMEZONE']['Hint'] = array(''=> translate('TZUnset')) + timezone_list(); + } # end if tab == system ?>
diff --git a/web/views/image.php b/web/views/image.php index 295b4b6c2..b05cc79d9 100644 --- a/web/views/image.php +++ b/web/views/image.php @@ -200,7 +200,7 @@ if ( empty($_REQUEST['path']) ) { header('HTTP/1.0 404 Not Found'); ZM\Fatal("Can't create frame images from video because there is no video file for this event at (".$Event->Path().'/'.$Event->DefaultVideo() ); } - $command ='ffmpeg -ss '. $Frame->Delta() .' -i '.$Event->Path().'/'.$Event->DefaultVideo().' -frames:v 1 '.$path; + $command = ZM_PATH_FFMPEG.' -ss '. $Frame->Delta() .' -i '.$Event->Path().'/'.$Event->DefaultVideo().' -frames:v 1 '.$path; #$command ='ffmpeg -ss '. $Frame->Delta() .' -i '.$Event->Path().'/'.$Event->DefaultVideo().' -vf "select=gte(n\\,'.$Frame->FrameId().'),setpts=PTS-STARTPTS" '.$path; #$command ='ffmpeg -v 0 -i '.$Storage->Path().'/'.$Event->Path().'/'.$Event->DefaultVideo().' -vf "select=gte(n\\,'.$Frame->FrameId().'),setpts=PTS-STARTPTS" '.$path; ZM\Logger::Debug("Running $command");