From e32b432fea28a524fa4805887035a32681a9ba59 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 11 Jun 2018 14:35:54 -0400 Subject: [PATCH 001/230] Always re-connect to the db on initialize --- scripts/ZoneMinder/lib/ZoneMinder/Logger.pm | 22 ++++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index f19d60bda..84e6c6012 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -439,16 +439,12 @@ sub databaseLevel { my $databaseLevel = shift; if ( defined($databaseLevel) ) { $databaseLevel = $this->limit($databaseLevel); - if ( $this->{databaseLevel} != $databaseLevel ) { - if ( $databaseLevel > NOLOG and $this->{databaseLevel} <= NOLOG ) { - if ( !$this->{dbh} ) { - $this->{dbh} = ZoneMinder::Database::zmDbConnect(); - } - } elsif ( $databaseLevel <= NOLOG && $this->{databaseLevel} > NOLOG ) { - undef($this->{dbh}); - } - $this->{databaseLevel} = $databaseLevel; + if ( $databaseLevel > NOLOG ) { + $this->{dbh} = ZoneMinder::Database::zmDbConnect(); + } elsif ( $databaseLevel <= NOLOG && $this->{databaseLevel} > NOLOG ) { + undef($this->{dbh}); } + $this->{databaseLevel} = $databaseLevel; } return $this->{databaseLevel}; } @@ -557,11 +553,13 @@ sub logPrint { if ( $level <= $this->{databaseLevel} ) { if ( ! ( $this->{dbh} and $this->{dbh}->ping() ) ) { $this->{sth} = undef; - if ( ! ( $this->{dbh} = ZoneMinder::Database::zmDbConnect() ) ) { - #print(STDERR "Can't log to database: "); - $this->{databaseLevel} = NOLOG; + + my $databaseLevel = $this->{databaseLevel}; + $this->{databaseLevel} = NOLOG; + if ( ! ( $this->{dbh} = ZoneMinder::Database::zmDbConnect(1) ) ) { return; } + $this->{databaseLevel} = $databaseLevel; } my $sql = 'INSERT INTO Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) VALUES ( ?, ?, ?, ?, ?, ?, ?, NULL )'; From d5d206f00a103868ce31fe616bf9db2f65d7efef Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 11 Jun 2018 15:57:38 -0400 Subject: [PATCH 002/230] Force disconnect when reinitialize Logging --- scripts/ZoneMinder/lib/ZoneMinder/Logger.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index 84e6c6012..3dc0592d4 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -440,7 +440,7 @@ sub databaseLevel { if ( defined($databaseLevel) ) { $databaseLevel = $this->limit($databaseLevel); if ( $databaseLevel > NOLOG ) { - $this->{dbh} = ZoneMinder::Database::zmDbConnect(); + $this->{dbh} = ZoneMinder::Database::zmDbConnect(1); } elsif ( $databaseLevel <= NOLOG && $this->{databaseLevel} > NOLOG ) { undef($this->{dbh}); } From f5026542f88149e33461b1fb3026a0a80bed8bd6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 11 Jun 2018 16:05:57 -0400 Subject: [PATCH 003/230] retest STDERR for terminal output on reinit. Simplify databaseLevel code to clear dbh always when not NOLOG. --- scripts/ZoneMinder/lib/ZoneMinder/Logger.pm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index 3dc0592d4..4bf3c5a16 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -310,7 +310,7 @@ sub reinitialise { # Bit of a nasty hack to reopen connections to log files and the DB my $syslogLevel = $this->syslogLevel(); - $this->syslogLevel( NOLOG ); + $this->syslogLevel(NOLOG); $this->syslogLevel($syslogLevel) if $syslogLevel > NOLOG; my $logfileLevel = $this->fileLevel(); @@ -321,11 +321,10 @@ sub reinitialise { $this->databaseLevel(NOLOG); $this->databaseLevel($databaseLevel) if $databaseLevel > NOLOG; - my $screenLevel = $this->termLevel(); + $this->{hasTerm} = -t STDERR; + my $termLevel = $this->termLevel(); $this->termLevel(NOLOG); - $this->termLevel($screenLevel) if $screenLevel > NOLOG; - - $this->{sth} = undef; + $this->termLevel($termLevel) if $termLevel > NOLOG; } # Prevents undefined logging levels @@ -440,10 +439,11 @@ sub databaseLevel { if ( defined($databaseLevel) ) { $databaseLevel = $this->limit($databaseLevel); if ( $databaseLevel > NOLOG ) { - $this->{dbh} = ZoneMinder::Database::zmDbConnect(1); - } elsif ( $databaseLevel <= NOLOG && $this->{databaseLevel} > NOLOG ) { + $this->{dbh} = ZoneMinder::Database::zmDbConnect(); + } else { undef($this->{dbh}); } + $this->{sth} = undef; $this->{databaseLevel} = $databaseLevel; } return $this->{databaseLevel}; From 90e4c2632da458751c81cb6a4caae87feac4eeae Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 12:08:50 -0400 Subject: [PATCH 004/230] Use Server->Url() more,, moving the logic into Server->Url() --- web/includes/Event.php | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/web/includes/Event.php b/web/includes/Event.php index d7ce25dc4..9c00b3670 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -189,25 +189,19 @@ class Event { public function getStreamSrc( $args=array(), $querySep='&' ) { - $streamSrc = ZM_BASE_PROTOCOL.'://'; + $streamSrc = ''; if ( $this->Storage()->ServerId() ) { $Server = $this->Storage()->Server(); - $streamSrc .= $Server->Hostname(); - if ( ZM_MIN_STREAMING_PORT ) { - $streamSrc .= ':'.(ZM_MIN_STREAMING_PORT+$this->{'MonitorId'}); - } } else if ( $this->Monitor()->ServerId() ) { # Assume that the server that recorded it has it $Server = $this->Monitor()->Server(); - $streamSrc .= $Server->Hostname(); - if ( ZM_MIN_STREAMING_PORT ) { - $streamSrc .= ':'.(ZM_MIN_STREAMING_PORT+$this->{'MonitorId'}); - } - } else if ( ZM_MIN_STREAMING_PORT ) { - $streamSrc .= $_SERVER['SERVER_NAME'].':'.(ZM_MIN_STREAMING_PORT+$this->{'MonitorId'}); } else { - $streamSrc .= $_SERVER['HTTP_HOST']; + $Server = new Server; } + $streamSrc .= $Server->Url( + ZM_MIN_STREAMING_PORT ? + ZM_MIN_STREAMING_PORT+$this->{'MonitorId'} : + null); if ( $this->{'DefaultVideo'} and $args['mode'] != 'jpeg' ) { $streamSrc .= ( ZM_BASE_PATH != '/' ? ZM_BASE_PATH : '' ).'/index.php'; @@ -319,25 +313,19 @@ class Event { # The thumbnail is theoretically the image with the most motion. # We always store at least 1 image when capturing - $streamSrc = ZM_BASE_PROTOCOL.'://'; + $streamSrc = ''; if ( $this->Storage()->ServerId() ) { $Server = $this->Storage()->Server(); - $streamSrc .= $Server->Hostname(); - if ( ZM_MIN_STREAMING_PORT ) { - $streamSrc .= ':'.(ZM_MIN_STREAMING_PORT+$this->{'MonitorId'}); - } } else if ( $this->Monitor()->ServerId() ) { + # Assume that the server that recorded it has it $Server = $this->Monitor()->Server(); - $streamSrc .= $Server->Hostname(); - if ( ZM_MIN_STREAMING_PORT ) { - $streamSrc .= ':'.(ZM_MIN_STREAMING_PORT+$this->{'MonitorId'}); - } - - } else if ( ZM_MIN_STREAMING_PORT ) { - $streamSrc .= $_SERVER['SERVER_NAME'].':'.(ZM_MIN_STREAMING_PORT+$this->{'MonitorId'}); } else { - $streamSrc .= $_SERVER['HTTP_HOST']; - } + $Server = new Server; + } + $streamSrc .= $Server->Url( + ZM_MIN_STREAMING_PORT ? + ZM_MIN_STREAMING_PORT+$this->{'MonitorId'} : + null); $streamSrc .= ( ZM_BASE_PATH != '/' ? ZM_BASE_PATH : '' ).'/index.php'; $args['eid'] = $this->{'Id'}; From 34299aff2c94e15c76e2d59743b44da943175e4e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 12:09:10 -0400 Subject: [PATCH 005/230] Add PathPrefix to Servers Table --- db/zm_create.sql.in | 1 + 1 file changed, 1 insertion(+) diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 83b75f768..d0482c479 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -556,6 +556,7 @@ DROP TABLE IF EXISTS `Servers`; CREATE TABLE `Servers` ( `Id` int(10) unsigned NOT NULL auto_increment, `Hostname` TEXT, + `PathPrefix` TEXT, `Name` varchar(64) NOT NULL default '', `State_Id` int(10) unsigned, `Status` enum('Unknown','NotRunning','Running') NOT NULL default 'Unknown', From 661876b9986ad2a567b8b9ee09fb3bbfab6ea861 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 12:09:29 -0400 Subject: [PATCH 006/230] Use Server->Url() more,, moving the logic into Server->Url() --- web/includes/Monitor.php | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 8fc69a9cd..bc9d92f8d 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -195,20 +195,13 @@ private $control_fields = array( } } - public function getStreamSrc( $args, $querySep='&' ) { + public function getStreamSrc($args, $querySep='&') { + + $streamSrc = $this->Server()->Url( + ZM_MIN_STREAMING_PORT ? + ZM_MIN_STREAMING_PORT+$this->{'Id'} : + null); - $streamSrc = ZM_BASE_PROTOCOL.'://'; - if ( isset($this->{'ServerId'}) and $this->{'ServerId'} ) { - $Server = new Server( $this->{'ServerId'} ); - $streamSrc .= $Server->Hostname(); - if ( ZM_MIN_STREAMING_PORT ) { - $streamSrc .= ':'.(ZM_MIN_STREAMING_PORT+$this->{'Id'}); - } - } else if ( ZM_MIN_STREAMING_PORT ) { - $streamSrc .= $_SERVER['SERVER_NAME'].':'.(ZM_MIN_STREAMING_PORT+$this->{'Id'}); - } else { - $streamSrc .= $_SERVER['HTTP_HOST']; - } $streamSrc .= ZM_PATH_ZMS; $args['monitor'] = $this->{'Id'}; @@ -230,9 +223,9 @@ private $control_fields = array( $args['rand'] = time(); } - $streamSrc .= '?'.http_build_query( $args,'', $querySep ); + $streamSrc .= '?'.http_build_query($args,'', $querySep); - return( $streamSrc ); + return $streamSrc; } // end function getStreamSrc public function Width($new = null) { @@ -487,9 +480,10 @@ private $control_fields = array( $source = preg_replace( '/^.*\//', '', $this->{'Path'} ); } elseif ( $this->{'Type'} == 'Ffmpeg' || $this->{'Type'} == 'Libvlc' || $this->{'Type'} == 'WebSite' ) { $url_parts = parse_url( $this->{'Path'} ); - if ( ZM_WEB_FILTER_SOURCE == "Hostname" ) { # Filter out everything but the hostname + if ( ZM_WEB_FILTER_SOURCE == 'Hostname' ) { # Filter out everything but the hostname $source = $url_parts['host']; - } elseif ( ZM_WEB_FILTER_SOURCE == "NoCredentials" ) { # Filter out sensitive and common items + } elseif ( ZM_WEB_FILTER_SOURCE == 'NoCredentials' ) { + # Filter out sensitive and common items unset($url_parts['user']); unset($url_parts['pass']); #unset($url_parts['scheme']); From 62f45b430e8b463b4cfadeff026ffa5a967121c0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 12:09:57 -0400 Subject: [PATCH 007/230] Add PathPrefix and use it in Url. Make Url() smarter so it can do more of the heavy lifting. --- web/includes/Server.php | 114 ++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 56 deletions(-) diff --git a/web/includes/Server.php b/web/includes/Server.php index d76984598..591b26f90 100644 --- a/web/includes/Server.php +++ b/web/includes/Server.php @@ -1,24 +1,26 @@ null, - 'Name' => '', - 'Hostname' => '', - 'zmaudit' => 1, - 'zmstats' => 1, - 'zmtrigger' => 0, + 'Id' => null, + 'Name' => '', + 'Hostname' => '', + 'PathPrefix' => '/zm', + 'zmaudit' => 1, + 'zmstats' => 1, + 'zmtrigger' => 0, ); - public function __construct( $IdOrRow = NULL ) { + + public function __construct($IdOrRow = NULL) { $row = NULL; if ( $IdOrRow ) { - if ( is_integer( $IdOrRow ) or ctype_digit( $IdOrRow ) ) { - $row = dbFetchOne( 'SELECT * FROM Servers WHERE Id=?', NULL, array( $IdOrRow ) ); - if ( ! $row ) { - Error("Unable to load Server record for Id=" . $IdOrRow ); + if ( is_integer($IdOrRow) or ctype_digit($IdOrRow) ) { + $row = dbFetchOne('SELECT * FROM Servers WHERE Id=?', NULL, array($IdOrRow)); + if ( !$row ) { + Error('Unable to load Server record for Id='.$IdOrRow); } - } elseif ( is_array( $IdOrRow ) ) { + } elseif ( is_array($IdOrRow) ) { $row = $IdOrRow; } } # end if isset($IdOrRow) @@ -27,11 +29,12 @@ class Server { $this->{$k} = $v; } } else { - $this->{'Name'} = ''; - $this->{'Hostname'} = ''; + # Set defaults + foreach ( $this->defaults as $k => $v ) $this->{$k} = $v; } } - public static function find_all( $parameters = null, $options = null ) { + + public static function find_all($parameters = null, $options = null) { $filters = array(); $sql = 'SELECT * FROM Servers '; $values = array(); @@ -42,9 +45,9 @@ class Server { foreach ( $parameters as $field => $value ) { if ( $value == null ) { $fields[] = $field.' IS NULL'; - } else if ( is_array( $value ) ) { + } else if ( is_array($value) ) { $func = function(){return '?';}; - $fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')'; + $fields[] = $field.' IN ('.implode(',', array_map($func, $value) ). ')'; $values += $value; } else { @@ -52,10 +55,10 @@ class Server { $values[] = $value; } } - $sql .= implode(' AND ', $fields ); + $sql .= implode(' AND ', $fields); } if ( $options and isset($options['order']) ) { - $sql .= ' ORDER BY ' . $options['order']; + $sql .= ' ORDER BY ' . $options['order']; } $result = dbQuery($sql, $values); $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Server'); @@ -65,20 +68,19 @@ class Server { return $filters; } - public function Url() { - if ( $this->Id() ) { - return ZM_BASE_PROTOCOL . '://'. $this->Hostname(); - } else { - return ZM_BASE_PROTOCOL . '://'. $_SERVER['SERVER_NAME']; - return ''; - } - } - public function Hostname() { - if ( isset( $this->{'Hostname'} ) and ( $this->{'Hostname'} != '' ) ) { - return $this->{'Hostname'}; - } - return $this->{'Name'}; - } + public function Url($port=null) { + return ZM_BASE_PROTOCOL.'://'.$this->Hostname().($port ? ':'.$port : '').$this->{'PathPrefix'}; + } + + public function Hostname() { + if ( isset( $this->{'Hostname'}) and ( $this->{'Hostname'} != '' ) ) { + return $this->{'Hostname'}; + } else if ( $this->Id() ) { + return $this->{'Name'}; + } + return $_SERVER['SERVER_NAME']; + } + public function __call($fn, array $args){ if ( count($args) ) { $this->{$fn} = $args[0]; @@ -86,49 +88,49 @@ class Server { if ( array_key_exists($fn, $this) ) { return $this->{$fn}; } else { - if ( array_key_exists( $fn, $this->defaults ) ) { + if ( array_key_exists($fn, $this->defaults) ) { return $this->defaults{$fn}; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; - Warning( "Unknown function call Server->$fn from $file:$line" ); + Warning("Unknown function call Server->$fn from $file:$line"); } } } - public static function find( $parameters = array(), $limit = NULL ) { + public static function find($parameters = array(), $limit = NULL) { $sql = 'SELECT * FROM Servers'; $values = array(); if ( sizeof($parameters) ) { - $sql .= ' WHERE ' . implode( ' AND ', array_map( + $sql .= ' WHERE ' . implode(' AND ', array_map( function($v){ return $v.'=?'; }, - array_keys( $parameters ) - ) ); - $values = array_values( $parameters ); + array_keys($parameters) + )); + $values = array_values($parameters); } - if ( is_integer( $limit ) or ctype_digit( $limit ) ) { - $sql .= ' LIMIT ' . $limit; - } else { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Error("Invalid value for limit($limit) passed to Server::find from $file:$line"); - return; - } - $results = dbFetchAll( $sql, NULL, $values ); + if ( is_integer($limit) or ctype_digit($limit) ) { + $sql .= ' LIMIT ' . $limit; + } else { + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; + Error("Invalid value for limit($limit) passed to Server::find from $file:$line"); + return; + } + $results = dbFetchAll($sql, NULL, $values); if ( $results ) { - return array_map( function($id){ return new Server($id); }, $results ); + return array_map(function($id){ return new Server($id); }, $results); } } - public static function find_one( $parameters = array() ) { - $results = Server::find( $parameters, 1 ); - if ( ! sizeof( $results ) ) { + public static function find_one($parameters = array()) { + $results = Server::find($parameters, 1); + if ( !sizeof($results) ) { return; } return $results[0]; } -} +} # end class Server ?> From d051342e9f629e8904e10763b517802c4a464b11 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 12:10:29 -0400 Subject: [PATCH 008/230] Add PathPrefix --- web/skins/classic/views/server.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/web/skins/classic/views/server.php b/web/skins/classic/views/server.php index ae78d622f..2791a3f64 100644 --- a/web/skins/classic/views/server.php +++ b/web/skins/classic/views/server.php @@ -24,7 +24,7 @@ if ( !canEdit( 'System' ) ) { } if ( $_REQUEST['id'] ) { - if ( !($newServer = dbFetchOne( 'SELECT * FROM Servers WHERE Id = ?', NULL, ARRAY($_REQUEST['id'])) ) ) { + if ( !($newServer = dbFetchOne('SELECT * FROM Servers WHERE Id = ?', NULL, ARRAY($_REQUEST['id']))) ) { $view = 'error'; return; } @@ -32,6 +32,7 @@ if ( $_REQUEST['id'] ) { $newServer = array(); $newServer['Name'] = translate('NewServer'); $newServer['Hostname'] = ''; + $newServer['PathPrefix'] = '/zm'; $newServer['zmstats'] = ''; $newServer['zmaudit'] = ''; $newServer['zmtrigger'] = ''; @@ -39,7 +40,7 @@ if ( $_REQUEST['id'] ) { $focusWindow = true; -xhtmlHeaders(__FILE__, translate('Server').' - '.$newServer['Name'] ); +xhtmlHeaders(__FILE__, translate('Server').' - '.$newServer['Name']); ?>
@@ -47,7 +48,7 @@ xhtmlHeaders(__FILE__, translate('Server').' - '.$newServer['Name'] );

-
+ @@ -61,6 +62,10 @@ xhtmlHeaders(__FILE__, translate('Server').' - '.$newServer['Name'] ); + + + + From acab621f2c43369ae3d01ec0df7c1847152afae5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 12:18:14 -0400 Subject: [PATCH 009/230] bump version for db update --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 856307b22..6701dcb1c 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.31.44 +1.31.45 From ebe55cf6a45762ee49fb0ac5d4ad6f99770fbc76 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 14:10:06 -0400 Subject: [PATCH 010/230] Include new Server.js --- web/js/Server.js | 12 ++++++++++++ web/skins/classic/includes/functions.php | 2 ++ 2 files changed, 14 insertions(+) create mode 100644 web/js/Server.js diff --git a/web/js/Server.js b/web/js/Server.js new file mode 100644 index 000000000..6e3254f65 --- /dev/null +++ b/web/js/Server.js @@ -0,0 +1,12 @@ +class Server { + constructor(json) { + for( var k in json ) { + this[k] = json[k]; + } + } + url(port=0){ + return location.protocol+'//'+this.Hostname+ + (port ? ':'+port : '') + + ( ( this.PathPrefix && this.PathPrefix != 'null') ? this.PathPrefix : ''); + } +}; diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index 66243daf3..e39df3ebe 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -119,6 +119,8 @@ echo output_link_if_exists( array( + + @@ -303,7 +304,7 @@ if (isset($_REQUEST['filter']['Query']['terms']['attr'])) { } ?>
  • >
  • -
  • +
  • @@ -321,7 +322,7 @@ if (isset($_REQUEST['filter']['Query']['terms']['attr'])) {
    -
    +
    > Date: Wed, 10 Oct 2018 14:01:36 -0400 Subject: [PATCH 031/230] Use Hostname instead of Url in test for CORS access. --- web/includes/functions.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/web/includes/functions.php b/web/includes/functions.php index e610ad8dd..0f49f611c 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -40,15 +40,16 @@ function CORSHeaders() { # The following is left for future reference/use. $valid = false; - $servers = dbFetchAll('SELECT * FROM Servers'); - if ( sizeof($servers) <= 1 ) { + $Servers = Server::find(); + if ( sizeof($Servers) <= 1 ) { # Only need CORSHeaders in the event that there are multiple servers in use. + # ICON: Might not be true. multi-port? return; } - foreach( $servers as $row ) { - $Server = new Server($row); - if ( preg_match('/^'.preg_quote($Server->Url(),'/').'/', $_SERVER['HTTP_ORIGIN']) ) { + foreach( $Servers as $Server ) { + if ( preg_match('/^(https?:\/\/)?'.preg_quote($Server->Hostname(),'/').'/', $_SERVER['HTTP_ORIGIN']) ) { $valid = true; + Logger::Debug("Setting Access-Controll-Allow-Origin from " . $_SERVER['HTTP_ORIGIN']); header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']); header('Access-Control-Allow-Headers: x-requested-with,x-request'); break; From a1ab0855db7ef6b031d432651210deea3d1dd859 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 10 Oct 2018 14:02:46 -0400 Subject: [PATCH 032/230] if navbar=0 is given in the url, don't output the navbar --- web/skins/classic/includes/functions.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index 84862d9d4..559e3e5bc 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -208,6 +208,9 @@ echo output_link_if_exists( array( } // end function xhtmlHeaders( $file, $title ) function getNavBarHTML($reload = null) { + # Provide a facility to turn off the headers if you put headers=0 into the url + if ( isset($_REQUEST['navbar']) and $_REQUEST['navbar']=='0' ) + return ''; $versionClass = (ZM_DYN_DB_VERSION&&(ZM_DYN_DB_VERSION!=ZM_VERSION))?'errorText':''; global $running; @@ -378,13 +381,13 @@ if ($reload == 'reload') ob_start();
    Date: Wed, 10 Oct 2018 14:03:27 -0400 Subject: [PATCH 033/230] use url instead of server_url in Monitor object --- web/skins/classic/views/js/montage.js | 6 +++--- web/skins/classic/views/js/montage.js.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index ecee73be0..2077b1097 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -3,11 +3,11 @@ var requestQueue = new Request.Queue( { concurrent: monitorData.length, stopOnFa function Monitor( monitorData ) { this.id = monitorData.id; this.connKey = monitorData.connKey; - this.server_url = monitorData.server_url; + this.url = monitorData.url; this.status = null; this.alarmState = STATE_IDLE; this.lastAlarmState = STATE_IDLE; - this.streamCmdParms = this.server_url+'?view=request&request=stream&connkey='+this.connKey; + this.streamCmdParms = '?view=request&request=stream&connkey='+this.connKey; this.onclick = monitorData.onclick; if ( auth_hash ) this.streamCmdParms += '&auth='+auth_hash; @@ -153,7 +153,7 @@ function Monitor( monitorData ) { if ( this.type != 'WebSite' ) { this.streamCmdReq = new Request.JSON( { - url: this.server_url, + url: this.url, method: 'get', timeout: 1000+AJAX_TIMEOUT, onSuccess: this.getStreamCmdResponse.bind( this ), diff --git a/web/skins/classic/views/js/montage.js.php b/web/skins/classic/views/js/montage.js.php index 3b0c5005b..183f17648 100644 --- a/web/skins/classic/views/js/montage.js.php +++ b/web/skins/classic/views/js/montage.js.php @@ -35,7 +35,7 @@ monitorData[monitorData.length] = { 'connKey': connKey() ?>, 'width': Width() ?>, 'height':Height() ?>, - 'server_url': 'Url() ?>', + 'url': 'Url() ?>', 'onclick': function(){createPopup( '?view=watch&mid=Id() ?>', 'zmWatchId() ?>', 'watch', Width(), $monitor->PopupScale() ); ?>, Height(), $monitor->PopupScale() ); ?> );}, 'type': 'Type() ?>', 'refresh': 'Refresh() ?>' From ad70648cb3d35d8b5249b6ad91f7331bac9addf8 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Wed, 10 Oct 2018 13:10:02 -0500 Subject: [PATCH 034/230] don't build neon instructions on armel (#2246) --- src/zm_image.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/zm_image.cpp b/src/zm_image.cpp index c4b76028e..0c3d8edcc 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -3317,11 +3317,11 @@ __attribute__((noinline)) void std_fastblend(const uint8_t* col1, const uint8_t* } /* FastBlend Neon for AArch32 */ -#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +#if (defined(__arm__) && !defined(__armel__) && !defined(ZM_STRIP_NEON)) __attribute__((noinline,__target__("fpu=neon"))) #endif void neon32_armv7_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) { -#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +#if (defined(__arm__) && !defined(__armel__) && !defined(ZM_STRIP_NEON)) static int8_t divider = 0; static double current_blendpercent = 0.0; @@ -3708,11 +3708,11 @@ __attribute__((noinline)) void std_delta8_abgr(const uint8_t* col1, const uint8_ } /* Grayscale Neon for AArch32 */ -#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +#if (defined(__arm__) && !defined(__armel__) && !defined(ZM_STRIP_NEON)) __attribute__((noinline,__target__("fpu=neon"))) #endif void neon32_armv7_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { -#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +#if (defined(__arm__) && !defined(__armel__) && !defined(ZM_STRIP_NEON)) /* Q0(D0,D1) = col1+0 */ /* Q1(D2,D3) = col1+16 */ @@ -3784,11 +3784,11 @@ __attribute__((noinline)) void neon64_armv8_delta8_gray8(const uint8_t* col1, co } /* RGB32 Neon for AArch32 */ -#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +#if (defined(__arm__) && !defined(__armel__) && !defined(ZM_STRIP_NEON)) __attribute__((noinline,__target__("fpu=neon"))) #endif void neon32_armv7_delta8_rgb32(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, uint32_t multiplier) { -#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +#if (defined(__arm__) && !defined(__armel__) && !defined(ZM_STRIP_NEON)) /* Q0(D0,D1) = col1+0 */ /* Q1(D2,D3) = col1+16 */ From c58f04399885407811b8cb71fce309ded300e79d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 10 Oct 2018 14:12:16 -0400 Subject: [PATCH 035/230] Add a mouseover tooltip saying Toggle Filters on up arrow --- web/skins/classic/views/montage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index 9f0cf8998..a0ebdce0a 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -135,7 +135,7 @@ xhtmlHeaders(__FILE__, translate('Montage'));
    From f4ddc0fe9b0ea557ea46d9c3416e48a9ec7e022e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 22 Oct 2018 16:13:12 -0400 Subject: [PATCH 067/230] ob_clcean and flush break outputting the image --- web/views/image.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/web/views/image.php b/web/views/image.php index 552b81256..ed54a3735 100644 --- a/web/views/image.php +++ b/web/views/image.php @@ -69,6 +69,7 @@ if ( empty($_REQUEST['path']) ) { } if ( !empty($_REQUEST['eid']) ) { +Logger::Debug("Loading by eid"); $Event = Event::find_one(array('Id'=>$_REQUEST['eid'])); if ( !$Event ) { header('HTTP/1.0 404 Not Found'); @@ -119,6 +120,7 @@ Logger::Debug("Got virtual frame from Bulk Frames previous delta: " . $previousB } // Frame can be non-existent. We have Bulk frames. So now we should try to load the bulk frame $path = $Event->Path().'/'.sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d',$Frame->FrameId()).'-'.$show.'.jpg'; +Logger::Debug("Path: $path"); } } else { @@ -208,12 +210,14 @@ if ( !empty($_REQUEST['scale']) ) { $width = 0; if ( !empty($_REQUEST['width']) ) { +Logger::Debug("Setting width: " . $_REQUEST['width']); if ( is_numeric($_REQUEST['width']) ) { $x = $_REQUEST['width']; if ( $x >= 10 and $x <= 8000 ) $width = $x; } } + $height = 0; if ( !empty($_REQUEST['height']) ) { if ( is_numeric($_REQUEST['height']) ) { @@ -229,14 +233,12 @@ if ( $errorText ) { # Clears the output buffer. Not sure what is there, but have had troubles. ob_end_clean(); header('Content-type: image/jpeg'); - if ( ( $scale==0 || $scale==100 ) && $width==0 && $height==0 ) { + if ( ( $scale==0 || $scale==100 ) && ($width==0) && ($height==0) ) { # This is so that Save Image As give a useful filename if ( $Event ) { $filename = $Event->MonitorId().'_'.$Event->Id().'_'.$Frame->FrameId().'.jpg'; header('Content-Disposition: inline; filename="' . $filename . '"'); } - ob_clean(); - flush(); if ( !readfile($path) ) { Error('No bytes read from '. $path); } @@ -254,6 +256,7 @@ if ( $errorText ) { $width = ($height * $oldWidth) / $oldHeight; } elseif ( $width != 0 && $height == 0 ) { $height = ($width * $oldHeight) / $oldWidth; +Logger::Debug("Figuring out height using width: $height = ($width * $oldHeight) / $oldWidth"); } if ( $width == $oldWidth && $height == $oldHeight ) { Warning('No change to width despite scaling.'); @@ -266,8 +269,6 @@ if ( $errorText ) { $filename = $Event->MonitorId().'_'.$Event->Id().'_'.$Frame->FrameId()."-${width}x${height}.jpg"; header('Content-Disposition: inline; filename="' . $filename . '"'); } - //ob_clean(); - //flush(); if ( !( file_exists($scaled_path) and readfile($scaled_path) ) ) { Logger::Debug("Cached scaled image does not exist at $scaled_path or is no good.. Creating it"); ob_start(); From 86b0e4ea188a018079a759908575630e99e103ff Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 22 Oct 2018 18:32:39 -0400 Subject: [PATCH 068/230] fix auth_hash. Should use generateAuthHash instead of accessing session directly --- web/skins/classic/js/skin.js.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/js/skin.js.php b/web/skins/classic/js/skin.js.php index f072a911e..b15ff51a5 100644 --- a/web/skins/classic/js/skin.js.php +++ b/web/skins/classic/js/skin.js.php @@ -55,7 +55,7 @@ if ( ! empty($refreshParent) ) { ?>; var closePopup = "; var auth_hash; -auth_hash = ''; +auth_hash = ''; From 18850d8779ebff0ce65e282189afb3e74531d367 Mon Sep 17 00:00:00 2001 From: raTmole Date: Tue, 23 Oct 2018 14:58:07 +0300 Subject: [PATCH 069/230] API getVersion Fix -> Undefined variable: eTagMatches... (#2268) see https://github.com/cakephp/cakephp/issues/12536 --- web/api/lib/Cake/Network/CakeResponse.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/api/lib/Cake/Network/CakeResponse.php b/web/api/lib/Cake/Network/CakeResponse.php index 21dfd7b2e..982398e87 100644 --- a/web/api/lib/Cake/Network/CakeResponse.php +++ b/web/api/lib/Cake/Network/CakeResponse.php @@ -1168,6 +1168,9 @@ class CakeResponse { if ($modifiedSince) { $timeMatches = strtotime($this->modified()) === strtotime($modifiedSince); } + if (!isset($etagMatches, $timeMatches)) { + return false; + } $checks = compact('etagMatches', 'timeMatches'); if (empty($checks)) { return false; From 2024df43931754b5b2088f91074f98ac26b5c933 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Oct 2018 09:49:56 -0400 Subject: [PATCH 070/230] use json_encode/decode instead of serialize/unserialize to pass onvif probe results around. Also clean up some code/ add some missing things. Fixes #2271 and #2272 --- web/lang/en_gb.php | 1 + web/skins/classic/views/monitor.php | 2 +- web/skins/classic/views/onvifprobe.php | 118 ++++++++++++------------- 3 files changed, 60 insertions(+), 61 deletions(-) diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 0fca2f81d..5412b94cd 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -237,6 +237,7 @@ $SLANG = array( 'Cause' => 'Cause', 'CheckMethod' => 'Alarm Check Method', 'ChooseDetectedCamera' => 'Choose Detected Camera', + 'ChooseDetectedProfile' => 'Choose Detected Profile', 'ChooseFilter' => 'Choose Filter', 'ChooseLogFormat' => 'Choose a log format', 'ChooseLogSelection' => 'Choose a log selection', diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index fa2ebd44f..193b08d36 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -173,7 +173,7 @@ if ( !empty($_REQUEST['preset']) ) { } } if ( !empty($_REQUEST['probe']) ) { - $probe = unserialize(base64_decode($_REQUEST['probe'])); + $probe = json_decode(base64_decode($_REQUEST['probe'])); foreach ( $probe as $name=>$value ) { if ( isset($value) ) { # Does isset handle NULL's? I don't think this code is correct. diff --git a/web/skins/classic/views/onvifprobe.php b/web/skins/classic/views/onvifprobe.php index f9c766846..ddbd895b6 100644 --- a/web/skins/classic/views/onvifprobe.php +++ b/web/skins/classic/views/onvifprobe.php @@ -18,7 +18,7 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -if ( !canEdit( 'Monitors' ) ) { +if ( !canEdit('Monitors') ) { $view = 'error'; return; } @@ -73,26 +73,26 @@ function probeCameras( $localIp ) { } elseif ( $tokens[1] == 'location' ) { // $camera['location'] = $tokens[2]; } else { - Logger::Debug('Unknown token ' . $tokens[1] ); + Logger::Debug('Unknown token ' . $tokens[1]); } } } // end foreach token $cameras[] = $camera; } } // end foreach line - } - return( $cameras ); -} + } // end if results from execOnvif + return $cameras; +} // end function probeCameras function probeProfiles( $device_ep, $soapversion, $username, $password ) { $profiles = array(); if ( $lines = @execONVIF( "profiles $device_ep $soapversion $username $password" ) ) { foreach ( $lines as $line ) { $line = rtrim( $line ); - if ( preg_match( '|^(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+)\s*$|', $line, $matches ) ) { + if ( preg_match('|^(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+)\s*$|', $line, $matches) ) { $stream_uri = $matches[7]; // add user@pass to URI - if ( preg_match( '|^(\S+://)(.+)$|', $stream_uri, $tokens ) ) { + if ( preg_match('|^(\S+://)(.+)$|', $stream_uri, $tokens) ) { $stream_uri = $tokens[1].$username.':'.$password.'@'.$tokens[2]; } @@ -106,17 +106,15 @@ function probeProfiles( $device_ep, $soapversion, $username, $password ) { 'Profile' => $matches[1], 'Name' => $matches[2], 'Encoding' => $matches[3], - ); $profiles[] = $profile; } else { Logger::Debug("Line did not match preg: $line"); } - } - } - return( $profiles ); -} - + } // end foreach line + } // end if results from execONVIF + return $profiles; +} // end function probeProfiles //==== STEP 1 ============================================================ @@ -124,20 +122,20 @@ $focusWindow = true; xhtmlHeaders(__FILE__, translate('MonitorProbe') ); -if( !isset($_REQUEST['step']) || ($_REQUEST['step'] == "1")) { +if ( !isset($_REQUEST['step']) || ($_REQUEST['step'] == '1') ) { $monitors = array(); - foreach ( dbFetchAll( "select Id, Name, Host from Monitors where Type = 'Remote' order by Host" ) as $monitor ) { - if ( preg_match( '/^(.+)@(.+)$/', $monitor['Host'], $matches ) ) { - //echo "1: ".$matches[2]." = ".gethostbyname($matches[2])."
    "; - $monitors[gethostbyname($matches[2])] = $monitor; - } else { - //echo "2: ".$monitor['Host']." = ".gethostbyname($monitor['Host'])."
    "; - $monitors[gethostbyname($monitor['Host'])] = $monitor; - } + foreach ( dbFetchAll("SELECT Id, Name, Host FROM Monitors WHERE Type = 'Remote' ORDER BY Host") as $monitor ) { + if ( preg_match( '/^(.+)@(.+)$/', $monitor['Host'], $matches ) ) { + //echo "1: ".$matches[2]." = ".gethostbyname($matches[2])."
    "; + $monitors[gethostbyname($matches[2])] = $monitor; + } else { + //echo "2: ".$monitor['Host']." = ".gethostbyname($monitor['Host'])."
    "; + $monitors[gethostbyname($monitor['Host'])] = $monitor; + } } - $detcameras = probeCameras( '' ); + $detcameras = probeCameras(''); foreach ( $detcameras as $camera ) { if ( preg_match( '|([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)|', $camera['monitor']['Host'], $matches ) ) { $ip = $matches[1]; @@ -157,13 +155,13 @@ if( !isset($_REQUEST['step']) || ($_REQUEST['step'] == "1")) { } */ // $sourceDesc = htmlspecialchars(serialize($camera['monitor'])); - $sourceDesc = base64_encode(serialize($camera['monitor'])); - $sourceString = $camera['model'].' @ '.$host . ' using version ' . $camera['monitor']['SOAP'] ; - $cameras[$sourceDesc] = $sourceString; + $sourceDesc = base64_encode(json_encode($camera['monitor'])); + $sourceString = $camera['model'].' @ '.$host . ' using version ' . $camera['monitor']['SOAP'] ; + $cameras[$sourceDesc] = $sourceString; } if ( count($cameras) <= 0 ) - $cameras[0] = translate('NoDetectedCameras'); + $cameras[0] = translate('NoDetectedCameras'); ?> @@ -180,22 +178,23 @@ if( !isset($_REQUEST['step']) || ($_REQUEST['step'] == "1")) {

    - + + 'configureButtons(this)')); ?>

    - +

    - +

    - +
    @@ -205,44 +204,42 @@ if( !isset($_REQUEST['step']) || ($_REQUEST['step'] == "1")) { $value ) { - if ( isset($value) ) { - $monitor[$name] = $value; - } + if ( isset($value) ) { + $monitor[$name] = $value; + } } $camera['monitor'] = $monitor; //print $monitor['Host'].", ".$_REQUEST['username'].", ".$_REQUEST['password']."
    "; - $detprofiles = probeProfiles( $monitor['Host'], $monitor['SOAP'], $_REQUEST['username'], $_REQUEST['password']); + $detprofiles = probeProfiles($monitor['Host'], $monitor['SOAP'], $_REQUEST['username'], $_REQUEST['password']); foreach ( $detprofiles as $profile ) { - $monitor = $camera['monitor']; - - $sourceString = "${profile['Name']} : ${profile['Encoding']}" . - " (${profile['Width']}x${profile['Height']} @ ${profile['MaxFPS']}fps)"; - // copy technical details - $monitor['Width'] = $profile['Width']; - $monitor['Height'] = $profile['Height']; -// The maxfps fields do not work for ip streams. Can re-enable if that is fixed. -// $monitor['MaxFPS'] = $profile['MaxFPS']; -// $monitor['AlarmMaxFPS'] = $profile['AlarmMaxFPS']; - $monitor['Path'] = $profile['Path']; -// $sourceDesc = htmlspecialchars(serialize($monitor)); - $sourceDesc = base64_encode(serialize($monitor)); - $profiles[$sourceDesc] = $sourceString; + $monitor = $camera['monitor']; + + $sourceString = "${profile['Name']} : ${profile['Encoding']}" . + " (${profile['Width']}x${profile['Height']} @ ${profile['MaxFPS']}fps)"; + // copy technical details + $monitor['Width'] = $profile['Width']; + $monitor['Height'] = $profile['Height']; + // The maxfps fields do not work for ip streams. Can re-enable if that is fixed. + // $monitor['MaxFPS'] = $profile['MaxFPS']; + // $monitor['AlarmMaxFPS'] = $profile['AlarmMaxFPS']; + $monitor['Path'] = $profile['Path']; + $sourceDesc = base64_encode(json_encode($monitor)); + $profiles[$sourceDesc] = $sourceString; } if ( count($profiles) <= 0 ) - $profiles[0] = translate('NoDetectedProfiles'); + $profiles[0] = translate('NoDetectedProfiles'); ?> @@ -254,17 +251,18 @@ else if($_REQUEST['step'] == "2")
    - +

    - + + 'configureButtons(this)')); ?>

    - + - +
    @@ -272,5 +270,5 @@ else if($_REQUEST['step'] == "2") From 115141bf9f13c2b7272f00651f7b6a0748059026 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Oct 2018 10:02:42 -0400 Subject: [PATCH 071/230] add caching to Group::find --- web/includes/Group.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/web/includes/Group.php b/web/includes/Group.php index df9fd611e..6c8338a55 100644 --- a/web/includes/Group.php +++ b/web/includes/Group.php @@ -95,13 +95,12 @@ class Group { } } } # end if options - $groups = array(); - $result = dbQuery($sql, $values); - $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Group'); - foreach ( $results as $row => $obj ) { - $groups[] = $obj; + + $results = dbFetchAll($sql, NULL, $values); + if ( $results ) { + return array_map( function($row){ return new Group($row); }, $results ); } - return $groups; + return array(); } # end find() public static function find_one($parameters = null, $options = null) { From bca2b305183470c56de7342f416b635620265f49 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Oct 2018 13:45:33 -0400 Subject: [PATCH 072/230] Add a test for channel layout and correct it if not set --- src/zm_videostore.cpp | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index a1c52353c..a3b66646b 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -248,18 +248,18 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, // Copy params from instream to ctx ret = avcodec_parameters_to_context(audio_out_ctx, audio_in_stream->codecpar); - if (ret < 0) { - Error("Unable to copy audio params to ctx %s\n", + if ( ret < 0 ) { + Error("Unable to copy audio params to ctx %s", av_make_error_string(ret).c_str()); } ret = avcodec_parameters_from_context(audio_out_stream->codecpar, audio_out_ctx); - if (ret < 0) { - Error("Unable to copy audio params to stream %s\n", + if ( ret < 0 ) { + Error("Unable to copy audio params to stream %s", av_make_error_string(ret).c_str()); } - if (!audio_out_ctx->codec_tag) { + if ( !audio_out_ctx->codec_tag ) { audio_out_ctx->codec_tag = av_codec_get_tag( oc->oformat->codec_tag, audio_in_ctx->codec_id); Debug(2, "Setting audio codec tag to %d", @@ -271,12 +271,12 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, ret = avcodec_copy_context(audio_out_ctx, audio_in_ctx); audio_out_ctx->codec_tag = 0; #endif - if (ret < 0) { - Error("Unable to copy audio ctx %s\n", + if ( ret < 0 ) { + Error("Unable to copy audio ctx %s", av_make_error_string(ret).c_str()); audio_out_stream = NULL; } else { - if (audio_out_ctx->channels > 1) { + if ( audio_out_ctx->channels > 1 ) { Warning("Audio isn't mono, changing it."); audio_out_ctx->channels = 1; } else { @@ -286,7 +286,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, } // end if audio_out_stream } // end if is AAC - if (audio_out_stream) { + if ( audio_out_stream ) { if (oc->oformat->flags & AVFMT_GLOBALHEADER) { #if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) audio_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; @@ -522,8 +522,8 @@ bool VideoStore::setup_resampler() { // audio_out_ctx = audio_out_stream->codec; audio_out_ctx = avcodec_alloc_context3(audio_out_codec); - if (!audio_out_ctx) { - Error("could not allocate codec ctx for AAC\n"); + if ( !audio_out_ctx ) { + Error("could not allocate codec ctx for AAC"); audio_out_stream = NULL; return false; } @@ -546,6 +546,13 @@ bool VideoStore::setup_resampler() { #else audio_out_ctx->refcounted_frames = 1; #endif + if ( ! audio_out_ctx->channel_layout ) { + Debug(3, "Correcting channel layout from (%d) to (%d)", + audio_out_ctx->channel_layout, + av_get_default_channel_layout(audio_out_ctx->channels) + ); + audio_out_ctx->channel_layout = av_get_default_channel_layout(audio_out_ctx->channels); + } if (audio_out_codec->supported_samplerates) { int found = 0; From d0080684b889ac4dbc4d30721681979ad175e573 Mon Sep 17 00:00:00 2001 From: Damir Merdan <44159556+dado-ca@users.noreply.github.com> Date: Wed, 24 Oct 2018 19:52:32 +0200 Subject: [PATCH 073/230] Bosnian translation (#2266) * Zoneminder bosnian translation Hi, this is a bosnian translation for zoneminder, but it can be used as croatian and serbian translation too * Delete BA_ba.php * Bosnian translation 80% translated, tested on my Zoneminder v1.30.4 installation and is looking good. ZmNinja also translated into Bosnian lagnuage. --- web/lang/ba_ba.php | 995 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 995 insertions(+) create mode 100644 web/lang/ba_ba.php diff --git a/web/lang/ba_ba.php b/web/lang/ba_ba.php new file mode 100644 index 000000000..e60e69066 --- /dev/null +++ b/web/lang/ba_ba.php @@ -0,0 +1,995 @@ + 'Dnevnik', + 'DateTime' => 'Datum/Vrijeme', + 'Component' => 'Komponenta', + 'Pid' => 'PID', + 'Level' => 'Nivo', + 'Message' => 'Poruka', + 'Line' => 'Linija', + 'More' => 'Više', + 'Clear' => 'Očisti', + '24BitColour' => '24 bitne boje', + '32BitColour' => '32 bitne boje', + '8BitGrey' => '8 bit siva nijansa', + 'Action' => 'Action', + 'Actual' => 'Stvarno', + 'AddNewControl' => 'Dodaj kontrolu', + 'AddNewMonitor' => 'Dodaj monitor', + 'AddNewServer' => 'Dodaj novi server', + 'AddNewStorage' => 'Dodaj novi disk', + 'AddNewUser' => 'Dodaj novog korisnika', + 'AddNewZone' => 'Dodaj novu zonu', + 'Alarm' => 'Alarm', + 'AlarmBrFrames' => 'Alarm
    Sličice', + 'AlarmFrame' => 'Alarm sličica', + 'AlarmFrameCount' => 'Brzina snimanja alarma (u frejmovima)', + 'AlarmLimits' => 'Alarm limiti', + 'AlarmMaximumFPS' => 'Alarm Max SPS', + 'AlarmPx' => 'Alarm Px', + 'AlarmRefImageBlendPct' => 'Alarm Reference Image Blend %ge', + 'AlarmRGBUnset' => 'Morate postaviti RGB boju za alarm', + 'Alert' => 'Uzbuna', + 'All' => 'Sve', + 'AnalysisFPS' => 'Analiza frejmova', + 'AnalysisUpdateDelay' => 'Analysis Update Delay', + 'Apply' => 'Primjeni', + 'ApplyingStateChange' => 'Primjenjujem promjenu stanja', + 'ArchArchived' => 'Samo arhivirano', + 'Archive' => 'Arhiva', + 'Archived' => 'Ahivirano', + 'ArchUnarchived' => 'Samo nearhivirano', + 'Area' => 'Oblast', + 'AreaUnits' => 'Oblast (px/%)', + 'AttrAlarmFrames' => 'Alarm frejmovi', + 'AttrArchiveStatus' => 'Status arhive', + 'AttrAvgScore' => 'Prosj. score', + 'AttrCause' => 'Uzrok', + 'AttrStartDate' => 'Pocetni datum', + 'AttrEndDate' => 'Krajnji datum', + 'AttrStartDateTime' => 'Pocetni Datum/Vrijeme', + 'AttrEndDateTime' => 'Krajnji Datum/Vrijeme', + 'AttrDiskSpace' => 'Disk prostor', + 'AttrDiskBlocks' => 'Disk blokovi', + 'AttrDiskPercent' => 'Disk procentualno', + 'AttrDuration' => 'Trajanje', + 'AttrFrames' => 'Frejmovi', + 'AttrId' => 'Id', + 'AttrMaxScore' => 'Max. Score', + 'AttrMonitorId' => 'ID Kamere', + 'AttrMonitorName' => 'Naziv Kamere', + 'AttrStorageArea' => 'Storage Area', + 'AttrFilterServer' => 'Server Filter je pokrenut na', + 'AttrMonitorServer' => 'Server Monitor je pokrenut na', + 'AttrStorageServer' => 'Server Hosting Storage', + 'AttrStateId' => 'Status', + 'AttrName' => 'Naziv', + 'AttrNotes' => 'Bilješke', + 'AttrSystemLoad' => 'Opterećenje sistema', + 'AttrStartTime' => 'Vrijeme početka', + 'AttrEndTime' => 'Vrijeme završetka', + 'AttrTotalScore' => 'Ukupan score', + 'AttrStartWeekday' => 'Početni dan', + 'AttrEndWeekday' => 'Krajnji dan', + 'Auto' => 'Automatski', + 'AutoStopTimeout' => 'Auto Stop Timeout', + 'Available' => 'Dostupno', + 'AvgBrScore' => 'Avg.
    Score', + 'Available' => 'Dostupno', + 'Background' => 'Pozadina', + 'BackgroundFilter' => 'Pokreni filter u pozadini', + 'BadAlarmFrameCount' => 'Brojač alarm frejmova mora biti tipa integer počevši od jedan ili više', + 'BadAlarmMaxFPS' => 'Max FPS za alarm mora biti pozitivan cjeli broj ili broj sa pomičnim zarezom', + 'BadAnalysisFPS' => 'Broj frejmova za analitiku mora pozitivan cjeli broj ili broj sa pomičnim zarezom', + 'BadAnalysisUpdateDelay'=> 'Vrijeme zadrške analitike mora biti broj od nula ili više', + 'BadChannel' => 'Kanal mora biti postavljen na cjeli broj nula ili više', + 'BadDevice' => 'Uredaj mora biti postavljen na validnu vrijednost', + 'BadFormat' => 'Format mora biti postavljen na validnu vrijenost', + 'BadFPSReportInterval' => 'FPS report interval buffer count must be an integer of 0 or more', + 'BadFrameSkip' => 'Frame skip count must be an integer of zero or more', + 'BadMotionFrameSkip' => 'Motion Frame skip count must be an integer of zero or more', + 'BadHeight' => 'Height must be set to a valid value', + 'BadHost' => 'Host must be set to a valid ip address or hostname, do not include http://', + 'BadImageBufferCount' => 'Image buffer size must be an integer of 10 or more', + 'BadLabelX' => 'Label X co-ordinate must be set to an integer of zero or more', + 'BadLabelY' => 'Label Y co-ordinate must be set to an integer of zero or more', + 'BadMaxFPS' => 'Maximum FPS must be a positive integer or floating point value', + 'BadNameChars' => 'Names may only contain alphanumeric characters plus spaces, hyphen and underscore', + 'BadPalette' => 'Palette must be set to a valid value', + 'BadColours' => 'Target colour must be set to a valid value', + 'BadPath' => 'Path must be set to a valid value', + 'BadPort' => 'Port must be set to a valid number', + 'BadPostEventCount' => 'Post event image count must be an integer of zero or more', + 'BadPreEventCount' => 'Pre event image count must be at least zero, and less than image buffer size', + 'BadRefBlendPerc' => 'Reference blend percentage must be a positive integer', + 'BadSectionLength' => 'Section length must be an integer of 30 or more', + 'BadSignalCheckColour' => 'Signal check colour must be a valid RGB colour string', + 'BadStreamReplayBuffer' => 'Stream replay buffer must be an integer of zero or more', + 'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"', + 'BadWarmupCount' => 'Warmup frames must be an integer of zero or more', + 'BadWebColour' => 'Web colour must be a valid web colour string', + 'BadWebSitePath' => 'Please enter a complete website url, including the http:// or https:// prefix.', + 'BadWidth' => 'Width must be set to a valid value', + 'Bandwidth' => 'Propusnost', + 'BandwidthHead' => 'propusnost', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing + 'BlobPx' => 'Blob Px', + 'Blobs' => 'Blobs', + 'BlobSizes' => 'Blob velicine', + 'Brightness' => 'Svjetloća', + 'Buffer' => 'Bufer', + 'Buffers' => 'Buferi', + 'CanAutoFocus' => 'Podržava Auto fokusiranje', + 'CanAutoGain' => 'Podržava Auto pojačanje', + 'CanAutoIris' => 'Podržava Auto blenda', + 'CanAutoWhite' => 'Podržava Auto balans bijel.', + 'CanAutoZoom' => 'Podržava Auto zum', + 'Cancel' => 'otkaži', + 'CancelForcedAlarm' => 'Otkaži prisilni alarm', + 'CanFocusAbs' => 'Podržava Abs fokus', + 'CanFocus' => 'Podržava Fokus', + 'CanFocusCon' => 'Podržava Kontinuirani fokus', + 'CanFocusRel' => 'Podržava Relativni fokus', + 'CanGainAbs' => 'Podržava Aps. pojačanje', + 'CanGain' => 'Podržava Pojačanje ', + 'CanGainCon' => 'Podržava Kontinuirano pojačanje', + 'CanGainRel' => 'Podržava Relativno pojačanje', + 'CanIrisAbs' => 'Podržava Aps. blenda', + 'CanIris' => 'Podržava Blenda', + 'CanIrisCon' => 'Podržava kontinuirana blenda', + 'CanIrisRel' => 'Podržava Relativna blenda', + 'CanMoveAbs' => 'Podržava Aps. kretanje', + 'CanMove' => 'Podržava Kretanje', + 'CanMoveCon' => 'Podržava Kontinuirano kretanje', + 'CanMoveDiag' => 'Podržava Dijagonalno kretanje', + 'CanMoveMap' => 'Podržava Mapirano kretanje', + 'CanMoveRel' => 'Podržava Relativno kretanje', + 'CanPan' => 'Podržava Pomak' , + 'CanReset' => 'PodržavaReset', + 'CanSetPresets' => 'Podržava presetove', + 'CanSleep' => 'Podržava Sleep', + 'CanTilt' => 'Podržava nagib', + 'CanWake' => 'Podržava Wake', + 'CanWhiteAbs' => 'Podržava Aps. balans bijele boje', + 'CanWhiteBal' => 'Podržava balans bijel.', + 'CanWhite' => 'Podržava bijelu', + 'CanWhiteCon' => 'Podržava kont. balans bijele boje', + 'CanWhiteRel' => 'Podržava relativ. balans bijele boje', + 'CanZoomAbs' => 'Podržava Aps. zoom', + 'CanZoom' => 'Podržava Zoom', + 'CanZoomCon' => 'Podržava kontinuirani Zoom', + 'CanZoomRel' => 'Podržava Relativni zoom', + 'CaptureHeight' => 'Visina slike', + 'CaptureMethod' => 'Metoda snimanja', + 'CaptureResolution' => 'Snimi rezoluciju', + 'CapturePalette' => 'Paleta boja', + 'CaptureWidth' => 'Širina slike', + 'Cause' => 'Uzrok', + 'CheckMethod' => 'Metoda provjere alarma', + 'ChooseDetectedCamera' => 'Odaberi otkrivenu kameru', + 'ChooseFilter' => 'Odaberi filter', + 'ChooseLogFormat' => 'Odaberi dugi format', + 'ChooseLogSelection' => 'Odaberi dugu selekciju', + 'ChoosePreset' => 'Odaberi preset', + 'CloneMonitor' => 'Kloniraj', + 'Close' => 'Zatvori', + 'Colour' => 'Bojs', + 'Command' => 'Komanda', + 'ConcurrentFilter' => 'Istovremeno pokreni filter', + 'Config' => 'Postavke', + 'ConfiguredFor' => 'Podešeno za', + 'ConfirmDeleteEvents' => 'Sigurni ste da želite izbrisati odabrane događaje?', + 'ConfirmPassword' => 'Potvrdi lozinku', + 'ConjAnd' => 'i', + 'ConjOr' => 'ili', + 'Console' => 'Konzola', + 'ContactAdmin' => 'Molimo konkatirajte svog administratora za detalje.', + 'Continue' => 'Nastavi', + 'Contrast' => 'Kontrast', + 'ControlAddress' => 'Kontrolna adresa', + 'ControlCap' => 'Control Capability', + 'ControlCaps' => 'Control Capabilities', + 'Control' => 'PTZ kontole', + 'ControlDevice' => 'Kontroliši uređaj', + 'Controllable' => 'Moguće kontrolisati', + 'ControlType' => 'Tipa kontrole', + 'Current' => 'Tekuće', + 'Cycle' => 'Kruži', + 'CycleWatch' => 'Kružni prikaz', + 'Day' => 'Dan', + 'Debug' => 'Debug', + 'DefaultRate' => 'Podrazumjevana stopa', + 'DefaultScale' => 'Podrazumjevani razmjer', + 'DefaultView' => 'Podrazumjevani prikaz', + 'Deinterlacing' => 'Deinterlacing', + 'RTSPDescribe' => 'Use RTSP Response Media URL', + 'Delay' => 'Zadrška', + 'DeleteAndNext' => 'Izbriši & Sljedeće', + 'DeleteAndPrev' => 'Izbriši & Preth', + 'Delete' => 'Izbriši', + 'DeleteSavedFilter' => 'Izbriši spremljeni filter', + 'Description' => 'Opis', + 'DetectedCameras' => 'Detektovane kamere:', + 'DetectedProfiles' => 'Otkriveni profili', + 'DeviceChannel' => 'Kanal', + 'DeviceFormat' => 'Sistem boja', + 'DeviceNumber' => 'Broj uređaja', + 'DevicePath' => 'Putanja uređaja', + 'Device' => 'Uređaj', + 'Devices' => 'Uređaji', + 'Dimensions' => 'Dimenzije', + 'DisableAlarms' => 'Onemogući alarme', + 'Disk' => 'Disk', + 'Display' => 'Prikaz', + 'Displaying' => 'Prikazujem', + 'DonateAlready' => 'Ne, već sam napravio donaciju.', + 'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.

    If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.

    Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.', + 'Donate' => 'Molimo donirajte', + 'DonateRemindDay' => 'Ne još, podsjetime za 1 dan', + 'DonateRemindHour' => 'Ne još, podsjetime za 1 sat', + 'DonateRemindMonth' => 'Ne još, podsjeti me za jedan mjesec', + 'DonateRemindNever' => 'Ne, ne želim donirati, nemoj me više podsjećati.', + 'DonateRemindWeek' => 'Ne još, podsjeti me za sedam dana.', + 'DonateYes' => 'Da, želim da doniram sada.', + 'DoNativeMotionDetection'=> 'Nativna detekcija pokreta', + 'Download' => 'Preuzmi', + 'DuplicateMonitorName' => 'Dupliciraj ime monitora', + 'Duration' => 'Trajanje', + 'Edit' => 'Uredi', + 'EditLayout' => 'Uredi raspored', + 'Email' => 'Email', + 'EnableAlarms' => 'Omogući alarme', + 'Enabled' => 'Omogućeno', + 'EnterNewFilterName' => 'Unesi novo ime za filter', + 'ErrorBrackets' => 'Greška, provjerite da li imate jednak broj otvorenih i zatvorenih zagrada.', + 'Error' => 'Greška', + 'ErrorValidValue' => 'Greška, osigurajte se da svi pojmovi imaju valide vrijednosti', + 'Etc' => 'itd', + 'Event' => 'Događaj', + 'EventFilter' => 'Filter događaja', + 'EventId' => 'ID događaja', + 'EventName' => 'Naziv događaja', + 'EventPrefix' => 'Prefiks događaja', + 'Events' => 'Događaji', + 'Exclude' => 'Isključi', + 'Execute' => 'Izvrši', + 'ExportDetails' => 'Izvezi detalje o događaju', + 'Exif' => 'Umetni EXIF podatke u sliku', + 'Export' => 'Izvezi', + 'DownloadVideo' => 'Preuzmi video', + 'GenerateDownload' => 'Generiši preuzimanje', + 'ExportFailed' => 'Izvoz nije uspio', + 'ExportFormat' => 'Format za izvoz', + 'ExportFormatTar' => 'Tar', + 'ExportFormatZip' => 'Zip', + 'ExportFrames' => 'Izvezi detalje frejma', + 'ExportImageFiles' => 'Izvezi slike', + 'ExportLog' => 'Izvezi zapisnik', + 'Exporting' => 'Izvozim', + 'ExportMiscFiles' => 'Izvezi druge fajlove (ukoliko postoje)', + 'ExportOptions' => 'Opcije izvoženja', + 'ExportSucceeded' => 'Izvoz uspio', + 'ExportVideoFiles' => 'Izvezi video fileove (ukoliko postoje)', + 'Far' => 'Far', + 'FastForward' => 'Naprijed', + 'Feed' => 'Feed', + 'Ffmpeg' => 'Ffmpeg', + 'File' => 'File', + 'FilterArchiveEvents' => 'Arhiviraj pronađeno', + 'FilterUpdateDiskSpace' => 'Ažuriraj korišteni prostor na disku', + 'FilterDeleteEvents' => 'Izbriši sve pronađeno', + 'FilterMoveEvents' => 'Premjesti pronađeno', + 'FilterEmailEvents' => 'Pošalji detalje mailom', + 'FilterExecuteEvents' => 'Izvrši sljededeću komandu', + 'FilterLog' => 'Filtriraj zapis', + 'FilterMessageEvents' => 'Message details of all matches', + 'FilterPx' => 'Filter Px', + 'Filter' => 'Filter', + 'Filters' => 'Filteri', + 'FilterUnset' => 'Morate navesti širinu i visinu filtera', + 'FilterUploadEvents' => 'Učitaj sve događaje', + 'FilterVideoEvents' => 'Napravi video', + 'First' => 'Prvi', + 'FlippedHori' => 'Zaokrenuto horizontalno', + 'FlippedVert' => 'Zaokrenuto vertikalno', + 'FnNone' => 'nijedan', // Added 2013.08.16. + 'FnMonitor' => 'Monitor', // Added 2013.08.16. + 'FnModect' => 'Modect', // Added 2013.08.16. + 'FnRecord' => 'Record', // Added 2013.08.16. + 'FnMocord' => 'Mocord', // Added 2013.08.16. + 'FnNodect' => 'Nodect', // Added 2013.08.16. + 'Focus' => 'Fokus', + 'ForceAlarm' => 'Prisilni alarm', + 'Format' => 'Format', + 'FPS' => 'fps', + 'FPSReportInterval' => 'FPS Report Interval', + 'Frame' => 'Frame', + 'FrameId' => 'Frame Id', + 'FrameRate' => 'Frame Rate', + 'Frames' => 'Frejmovi', + 'FrameSkip' => 'Preskoči frejm', + 'MotionFrameSkip' => 'Motion Frame Skip', + 'FTP' => 'FTP', + 'Func' => 'Func', + 'Function' => 'Funkcija', + 'Gain' => 'Pojačanje', + 'General' => 'Opšte', + 'GenerateVideo' => 'Generiši video', + 'GeneratingVideo' => 'Generiši video', + 'GoToZoneMinder' => 'Idi na ZoneMinder.com', + 'Grey' => 'Siva', + 'Group' => 'Grupa', + 'Groups' => 'Grupe', + 'HasFocusSpeed' => 'Posjeduje brzo fokusiranja', + 'HasGainSpeed' => 'Posjeduje brzo pojačanja', + 'HasHomePreset' => 'Has Home Preset', + 'HasIrisSpeed' => 'Posjeduje brzu blendu', + 'HasPanSpeed' => 'Posjeduje brzi pomak', + 'HasPresets' => 'Posjeduje pre-setove', + 'HasTiltSpeed' => 'Posjeduje brzi nagiba', + 'HasTurboPan' => 'Posjeduje turbo pomak', + 'HasTurboTilt' => 'Posjeduje turbo nagib', + 'HasWhiteSpeed' => 'Posjeduje brzo podeš.bijele', + 'HasZoomSpeed' => 'Posjeduje brzi zoom', + 'HighBW' => 'High B/W', + 'High' => 'veliku', + 'Home' => 'Početna', + 'Hostname' => 'Hostname', + 'Hour' => 'Sat', + 'Hue' => 'Nijansa', + 'Id' => 'Id', + 'Idle' => 'Na čekanju', + 'Ignore' => 'Zanemari', + 'ImageBufferSize' => 'Veličina slikovnog bufera (u frejmovima)', + 'Image' => 'Slika', + 'Images' => 'Slike', + 'Include' => 'Uključi', + 'In' => 'U', + 'Inverted' => 'Invertirano', + 'Iris' => 'Blenda', + 'KeyString' => 'Key String', + 'Label' => 'Oznaka', + 'Language' => 'Jezik', + 'Last' => 'Zadnje', + 'Layout' => 'Raspored', + 'Libvlc' => 'Libvlc', + 'LimitResultsPost' => 'results only', // This is used at the end of the phrase 'Limit to first N results only' + 'LimitResultsPre' => 'Limit to first', // This is used at the beginning of the phrase 'Limit to first N results only' + 'LinkedMonitors' => 'Povezani monitori', + 'List' => 'Popis', + 'ListMatches' => 'Prikaži pronađeno', + 'Load' => 'Opterećenje', + 'Local' => 'Lokalno', + 'Log' => 'Zapis', + 'Logs' => 'Zapisi', + 'Logging' => 'Dnevnik događaja', + 'LoggedInAs' => 'Prijavljen kao', + 'LoggingIn' => 'Prijavljujem', + 'Login' => 'prijava', + 'Logout' => 'odjava', + 'LowBW' => 'Low B/W', + 'Low' => 'nisku', + 'Main' => 'Glavno', + 'Man' => 'Man', + 'Manual' => 'Ručno', + 'Mark' => 'Označi', + 'MaxBandwidth' => 'Max propusnost', + 'MaxBrScore' => 'Max.
    Score', + 'MaxFocusRange' => 'Max raspon fokusa', + 'MaxFocusSpeed' => 'Max brzina fokusa', + 'MaxFocusStep' => 'Max korak fokusa', + 'MaxGainRange' => 'Max raspon pojačanja', + 'MaxGainSpeed' => 'Max brzina pojačanja', + 'MaxGainStep' => 'Max korak pojačanja', + 'MaximumFPS' => 'Maximum FPS', + 'MaxIrisRange' => 'Max raspon blende', + 'MaxIrisSpeed' => 'Max brzina blende', + 'MaxIrisStep' => 'Max korak blende', + 'Max' => 'Max', + 'MaxPanRange' => 'Max raspon pomaka', + 'MaxPanSpeed' => 'Max brzina pomaka', + 'MaxPanStep' => 'Max korak pomaka', + 'MaxTiltRange' => 'Max raspon nagiba', + 'MaxTiltSpeed' => 'Max brzina nagiba', + 'MaxTiltStep' => 'Max korak nagiba', + 'MaxWhiteRange' => 'Max raspon bijele', + 'MaxWhiteSpeed' => 'Max brzina bijele', + 'MaxWhiteStep' => 'Max korak bijele', + 'MaxZoomRange' => 'Max raspon zumiranja', + 'MaxZoomSpeed' => 'Max brzina zumiranja', + 'MaxZoomStep' => 'Max korak zumiranja', + 'MediumBW' => 'Medium B/W', + 'Medium' => 'srednju', + 'MinAlarmAreaLtMax' => 'Min područje alarma mora biti manje od maksimalnog', + 'MinAlarmAreaUnset' => 'Morate zadati minimalni broj alarm piksela', + 'MinBlobAreaLtMax' => 'Min blob područje mora biti manje od maksimalnog', + 'MinBlobAreaUnset' => 'Morate zadati minimalni broj blob piksela', + 'MinBlobLtMinFilter' => 'Min blob oblast mora biti manja ili jednaka minimalnoj oblasti filtera', + 'MinBlobsLtMax' => 'Min blob mora biti manji od maksimalne', + 'MinBlobsUnset' => 'morate zadati minimalni broj blob-ova', + 'MinFilterAreaLtMax' => 'Minimalna oblast filtera mora biti manja od maksimalne', + 'MinFilterAreaUnset' => 'Morate zadati minimalni broj filter piksela', + 'MinFilterLtMinAlarm' => 'Min oblast filtera mora biti manja ili jednaka minimalnoj oblasti alarmne oblasti', + 'MinFocusRange' => 'Min raspon fokusiranja', + 'MinFocusSpeed' => 'Min brzina fokusiranja', + 'MinFocusStep' => 'Min korak fokusiranja', + 'MinGainRange' => 'Min raspon pojačanja', + 'MinGainSpeed' => 'Min brzina pojačanja', + 'MinGainStep' => 'Min korak pojačanja', + 'MinIrisRange' => 'Min raspon blende', + 'MinIrisSpeed' => 'Min brzina blende', + 'MinIrisStep' => 'Min korak blende', + 'MinPanRange' => 'Min raspon pomaka', + 'MinPanSpeed' => 'Min brzina pomaka', + 'MinPanStep' => 'Min korak pomaka', + 'MinPixelThresLtMax' => 'Min prag piksela mora biti manji od maksimalnog', + 'MinPixelThresUnset' => 'Morate zadati minimalni prag piksela', + 'MinTiltRange' => 'Min Tilt Range', + 'MinTiltSpeed' => 'Min Tilt Speed', + 'MinTiltStep' => 'Min Tilt Step', + 'MinWhiteRange' => 'Min raspon bijelog balansa', + 'MinWhiteSpeed' => 'Min brzina bijelog balansa', + 'MinWhiteStep' => 'Min White Bal. Step', + 'MinZoomRange' => 'Min raspon zumiranja', + 'MinZoomSpeed' => 'Min brzina zumiranja', + 'MinZoomStep' => 'Min korak zumiranja', + 'Misc' => 'Razno', + 'Mode' => 'Modus', + 'MonitorIds' => 'Monitor Ids', + 'Monitor' => 'Monitor', + 'MonitorPresetIntro' => 'Odaberite odgovarajuće pre-setove sa popisa.

    Imajte u vidu da ovo može prepisati bilo koju vrijednost koja postoji za odabrane monitore.

    ', + 'MonitorPreset' => 'Monitor Preset', + 'MonitorProbeIntro' => 'Donji popis prikazuje otkrivene analogne i mrežne kamere, te da li se iste već koriste i da li su dostupne.

    Odaberite željenu kameru sa donjeg popisa.

    Imajte u vidu da ovo može prepisati bilo koju vrijednost koja postoji za odabrane monitore.

    ', + 'MonitorProbe' => 'Detektuj kameru', + 'Monitors' => 'Monitori', + 'Montage' => 'Montage', + 'MontageReview' => 'Montage pregled', + 'Month' => 'Mjesec', + 'Move' => 'Pomjeri', + 'MtgDefault' => 'Podrazumjevano', // Added 2013.08.15. + 'Mtg2widgrd' => '2-struka rešetka', // Added 2013.08.15. + 'Mtg3widgrd' => '3-struka rešetka', // Added 2013.08.15. + 'Mtg4widgrd' => '4-struka rešetka', // Added 2013.08.15. + 'Mtg3widgrx' => '3-wide grid, scaled, enlarge on alarm', // Added 2013.08.15. + 'MustBeGe' => 'mora biti veće ili jednako', + 'MustBeLe' => 'mora biti manje ili jednako', + 'MustConfirmPassword' => 'Morate potvrditi lozinku', + 'MustSupplyPassword' => 'Morate unjeti lozinku', + 'MustSupplyUsername' => 'Morate unjeti korisničko ime', + 'Name' => 'Ime', + 'Near' => 'Blizu', + 'Network' => 'Mreža', + 'NewGroup' => 'Nova grupa', + 'NewLabel' => 'Nova oznaka', + 'New' => 'Novo', + 'NewPassword' => 'Nova lozinka', + 'NewState' => 'Novi radni modus', + 'NewUser' => 'Novi korisnik', + 'Next' => 'Sljedeće', + 'NoDetectedCameras' => 'Nema otkrivenih kamera', + 'NoDetectedProfiles' => 'Nema otkrivenih profila', + 'NoFramesRecorded' => 'Nije ništa snimljeno za ovaj događaj', + 'NoGroup' => 'Nema grupe', + 'NoneAvailable' => 'Nijedno dostupno', + 'None' => 'Nijedno', + 'No' => 'Ne', + 'Normal' => 'Normalno', + 'NoSavedFilters' => 'NemaSnimljenihFiltera', + 'NoStatisticsRecorded' => 'Nema snimljenih statistika za ovaj događaj', + 'Notes' => 'Bilješke', + 'NumPresets' => 'Num Presets', + 'Off' => 'Isključeno', + 'On' => 'Uključeno', + 'OnvifProbe' => 'ONVIF detekcija', + 'OnvifProbeIntro' => 'The list below shows detected ONVIF cameras and whether they are already being used or available for selection.

    Select the desired entry from the list below.

    Please note that not all cameras may be detected and that choosing a camera here may overwrite any values you already have configured for the current monitor.

    ', + 'OnvifCredentialsIntro' => 'Please supply user name and password for the selected camera.
    If no user has been created for the camera then the user given here will be created with the given password.

    ', + 'Open' => 'Otvori', + 'OpEq' => 'jednako', + 'OpGtEq' => 'veće ili jednako od', + 'OpGt' => 'veće ', + 'OpIn' => 'in set', + 'OpLtEq' => 'manje ili jednako od', + 'OpLt' => 'manje od', + 'OpMatches' => 'matches', + 'OpNe' => 'nije jednako', + 'OpNotIn' => 'nije u ', + 'OpNotMatches' => 'ne poklapa se', + 'OpIs' => 'je', + 'OpIsNot' => 'nije', + 'OptionalEncoderParam' => 'Opcionalni parametri enkodera', + 'OptionHelp' => 'Option Help', + 'OptionRestartWarning' => 'These changes may not come into effect fully\nwhile the system is running. When you have\nfinished making your changes please ensure that\nyou restart ZoneMinder.', + 'Options' => 'Opcije', + 'Order' => 'Redosljed', + 'OrEnterNewName' => 'ili unesi novo ime', + 'Orientation' => 'Orijentacija', + 'Out' => 'Izlaz', + 'OverwriteExisting' => 'Prepiši preko postojećeg', + 'Paged' => 'stranično', + 'PanLeft' => 'Pomak lijevo', + 'Pan' => 'Pomak', + 'PanRight' => 'Pomak desno', + 'PanTilt' => 'Pomak/Nagib', + 'Parameter' => 'Parametar', + 'Password' => 'Lozinka', + 'PasswordsDifferent' => 'Nova i potvrđena lozinka se razlikuju', + 'Paths' => 'Putanje', + 'Pause' => 'Pauza', + 'PhoneBW' => 'Telefon B/W', + 'Phone' => 'Telefon', + 'PixelDiff' => 'Piksel razli.', + 'Pixels' => 'pikseli', + 'PlayAll' => 'play all', + 'Play' => 'Play', + 'Plugins' => 'Plugini', + 'PleaseWait' => 'Molim čekati', + 'Point' => 'Point', + 'PostEventImageBuffer' => 'Br. frejmova poslije događaja', + 'PreEventImageBuffer' => 'Br. frejmova prije događaja', + 'PreserveAspect' => 'Zadrži omjer', + 'Preset' => 'Preset', + 'Presets' => 'Presets', + 'Prev' => 'Preth', + 'Privacy' => 'Privatnost', + 'PrivacyAbout' => 'O', + 'PrivacyAboutText' => 'Since 2002, ZoneMinder has been the premier free and open-source Video Management System (VMS) solution for Linux platforms. ZoneMinder is supported by the community and is managed by those who choose to volunteer their spare time to the project. The best way to improve ZoneMinder is to get involved.', + 'PrivacyContact' => 'Konakt', + 'PrivacyContactText' => 'Please contact us here for any questions regarding our privacy policy or to have your information removed.

    For support, there are three primary ways to engage with the community:

    Our Github forum is only for bug reporting. Please use our user forum or slack channel for all other questions or comments.

    ', + 'PrivacyCookies' => 'Kolačići', + 'PrivacyCookiesText' => 'Whether you use a web browser or a mobile app to communicate with the ZoneMinder server, a ZMSESSID cookie is created on the client to uniquely identify a session with the ZoneMinder server. ZmCSS and zmSkin cookies are created to remember your style and skin choices.', + 'PrivacyTelemetry' => 'Telemetry', + 'PrivacyTelemetryText' => 'Because ZoneMinder is open-source, anyone can install it without registering. This makes it difficult to answer questions such as: how many systems are out there, what is the largest system out there, what kind of systems are out there, or where are these systems located? Knowing the answers to these questions, helps users who ask us these questions, and it helps us set priorities based on the majority user base.', + 'PrivacyTelemetryList' => 'The ZoneMinder Telemetry daemon collects the following data about your system:
    • A unique identifier (UUID)
    • City based location is gathered by querying ipinfo.io. City, region, country, latitude, and longitude parameters are saved. The latitude and longitude coordinates are accurate down to the city or town level only!
    • Current time
    • Total number of monitors
    • Total number of events
    • System architecture
    • Operating system kernel, distro, and distro version
    • Version of ZoneMinder
    • Total amount of memory
    • Number of cpu cores
    ', + 'PrivacyMonitorList' => 'The following configuration parameters from each monitor are collected:
    • Id
    • Name
    • Type
    • Function
    • Width
    • Height
    • Colours
    • MaxFPS
    • AlarmMaxFPS
    ', + 'PrivacyConclusionText' => 'We are NOT collecting any image specific data from your cameras. We don�t know what your cameras are watching. This data will not be sold or used for any purpose not stated herein. By clicking accept, you agree to send us this data to help make ZoneMinder a better product. By clicking decline, you can still freely use ZoneMinder and all its features.', + 'Probe' => 'Detektuj kameru', + 'ProfileProbe' => 'Stream proba', + 'ProfileProbeIntro' => 'The list below shows the existing stream profiles of the selected camera .

    Select the desired entry from the list below.

    Please note that ZoneMinder cannot configure additional profiles and that choosing a camera here may overwrite any values you already have configured for the current monitor.

    ', + 'Progress' => 'Napredak', + 'Protocol' => 'Protkol', + 'Rate' => 'Stopa', + 'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // added Sep 24 2015 - PP + 'RecordAudio' => 'Whether to store the audio stream when saving an event.', + 'Real' => 'Stvarno', + 'Record' => 'Snimaj', + 'RefImageBlendPct' => 'Reference Image Blend %ge', + 'Refresh' => 'Osvježi', + 'RemoteHostName' => 'Naziv uređaja', + 'RemoteHostPath' => 'Putanja', + 'RemoteHostSubPath' => 'Pod-putanja', + 'RemoteHostPort' => 'Port', + 'RemoteImageColours' => 'Boje slike', + 'RemoteMethod' => 'Metoda', + 'RemoteProtocol' => 'Protokol', + 'Remote' => 'Udaljeno', + 'Rename' => 'Preimenuj', + 'ReplayAll' => 'Svi događaji', + 'ReplayGapless' => 'Gapless Events', + 'Replay' => 'Ponovo odigraj', + 'ReplaySingle' => 'Jedan događaj', + 'ReportEventAudit' => 'Audit Events Report', + 'ResetEventCounts' => 'Resetiraj događaje', + 'Reset' => 'Reset', + 'Restarting' => 'Restartiram', + 'Restart' => 'Restaruj', + 'RestrictedCameraIds' => 'Restricted Camera Ids', + 'RestrictedMonitors' => 'Ograničeni monitori', + 'ReturnDelay' => 'Vrati kašnjenje', + 'ReturnLocation' => 'Vrati lokaciju', + 'Rewind' => 'Premotaj', + 'RotateLeft' => 'Rotoraj ulijevo', + 'RotateRight' => 'Rotiraj udesno', + 'RTSPTransport' => 'RTSP Transport Protocol', + 'RunAudit' => 'Run Audit Process', + 'RunLocalUpdate' => 'Pokrenite zmupdate.pl za ažuriranje', + 'RunMode' => 'Modus rada', + 'Running' => 'Pokrenuto', + 'RunState' => 'Radni modus', + 'RunStats' => 'Pokreni stats proces', + 'RunTrigger' => 'Pokreni triger proces', + 'SaveAs' => 'Spremi kao', + 'SaveFilter' => 'Spremi Filter', + 'SaveJPEGs' => 'Spremi JPEGs', + 'Save' => 'Spremi', + 'Scale' => 'Razmjer', + 'Score' => 'Zbir', + 'Secs' => 'Secs', + 'Sectionlength' => 'Odaberi dužinu', + 'SelectMonitors' => 'SOdaberi monitore', + 'Select' => 'Odaberi', + 'SelectFormat' => 'Odaberi format', + 'SelectLog' => 'Odaberi zapis', + 'SelfIntersecting' => 'Polygon edges must not intersect', + 'SetNewBandwidth' => 'Postavi propusnost na', + 'SetPreset' => 'Postavi pozicije', + 'Set' => 'Postavi', + 'Settings' => 'Postavke', + 'ShowFilterWindow' => 'Prikaži prozor za filter', + 'ShowTimeline' => 'Prikaži vremensku liniju', + 'SignalCheckColour' => 'Signal Check Colour', + 'SignalCheckPoints' => 'Signal Check Points', + 'Size' => 'Veličina', + 'SkinDescription' => 'Izmjeni izgled za ovu sesiju', + 'CSSDescription' => 'Izmjeni css za ovu sesiju', + 'Sleep' => 'Sleep', + 'SortAsc' => 'Rastuće', + 'SortBy' => 'Sortiraj po', + 'SortDesc' => 'Padajuće', + 'Source' => 'Izvor', + 'SourceColours' => 'Source Colours', + 'SourcePath' => 'Putanja izvora ', + 'SourceType' => 'Izvor videa', + 'SpeedHigh' => 'Velika brzina', + 'SpeedLow' => 'Niska brzina', + 'SpeedMedium' => 'Srednja brzina', + 'Speed' => 'brzina', + 'SpeedTurbo' => 'Turbo brzina', + 'Start' => 'Start', + 'State' => 'Stanje', + 'Stats' => 'Statistka', + 'Status' => 'Status', + 'StatusUnknown' => 'Nepoznato', + 'StatusConnected' => 'Snimam', + 'StatusNotRunning' => 'Nije pokrenuto', + 'StatusRunning' => 'Ne snima', + 'StepBack' => 'Korak nazad', + 'StepForward' => 'Korak naprijed', + 'StepLarge' => 'Veliki korak', + 'StepMedium' => 'Srednji korak', + 'StepNone' => 'Bez koraka', + 'StepSmall' => 'Mali korak', + 'Step' => 'Korak', + 'Stills' => 'Stills', + 'Stopped' => 'Zaustavljeno', + 'Stop' => 'Zaustavi', + 'StorageArea' => 'Storage Area', + 'StorageDoDelete' => 'Brisanja', + 'StorageScheme' => 'Šema', + 'StreamReplayBuffer' => 'Stream Replay Image Buffer', + 'Stream' => 'Stream', + 'Submit' => 'Pošalji', + 'System' => 'Sistem', + 'TargetColorspace' => 'Rezolucija boja', + 'Tele' => 'Udaljeno', + 'Thumbnail' => 'Sličica', + 'Tilt' => 'Tilt', + 'TimeDelta' => 'Vremenska razlika', + 'Timeline' => 'Vremenska linija', + 'TimelineTip1' => 'Pass your mouse over the graph to view a snapshot image and event details.', // Added 2013.08.15. + 'TimelineTip2' => 'Click on the coloured sections of the graph, or the image, to view the event.', // Added 2013.08.15. + 'TimelineTip3' => 'Click on the background to zoom in to a smaller time period based around your click.', // Added 2013.08.15. + 'TimelineTip4' => 'Use the controls below to zoom out or navigate back and forward through the time range.', // Added 2013.08.15. + 'TimestampLabelFormat' => 'Timestamp format oznake', + 'TimestampLabelX' => 'Timestamp oznaka X', + 'TimestampLabelY' => 'Timestamp oznaka Y', + 'TimestampLabelSize' => 'Veličina fonta', + 'Timestamp' => 'Timestamp', + 'TimeStamp' => 'Vremenski pečat', + 'Time' => 'Vrijeme', + 'Today' => 'Danas', + 'Tools' => 'Alati', + 'Total' => 'Ukupno', + 'TotalBrScore' => 'Total
    Score', + 'TrackDelay' => 'Kašnjenje', + 'TrackMotion' => 'Prati pokret', + 'Triggers' => 'Okidači', + 'TurboPanSpeed' => 'Turbo Pan brzina', + 'TurboTiltSpeed' => 'Turbo Tilt brzina', + 'Type' => 'Tip', + 'Unarchive' => 'Dearhiviraj', + 'Undefined' => 'Nedefinisano', + 'Units' => 'Mjere', + 'Unknown' => 'Nepoznato', + 'UpdateAvailable' => 'Dostupno je novo ažurranje za Zoneminder .', + 'UpdateNotNecessary' => 'Ažuriranje nije potrebno.', + 'Update' => 'Ažuiriaj', + 'Upload' => 'Upload', + 'Updated' => 'Ažurirano', + 'UsedPlugins' => 'Korišteni plugini ', + 'UseFilterExprsPost' => ' filter expressions', // This is used at the end of the phrase 'use N filter expressions' + 'UseFilterExprsPre' => 'Use ', // This is used at the beginning of the phrase 'use N filter expressions' + 'UseFilter' => 'Koristi filter', + 'Username' => 'Korisničko ime', + 'Users' => 'Korisnici', + 'User' => 'Korisnik', + 'Value' => 'Vrijednost', + 'VersionIgnore' => 'Ignoriši ovu verziju', + 'VersionRemindDay' => 'Podsjeti me za jedan dan', + 'VersionRemindHour' => 'Podsjeti me za jedan sat', + 'VersionRemindNever' => 'Ne podsjecaj me na nove verzije', + 'VersionRemindWeek' => 'Podsjeti me za sedam dana', + 'Version' => 'Verzija', + 'VideoFormat' => 'Video Format', + 'VideoGenFailed' => 'Generisanje videa nije uspjelo!', + 'VideoGenFiles' => 'Postojece video datoteke', + 'VideoGenNoFiles' => 'Video datoteke nisu pronadjene', + 'VideoGenParms' => 'Parametri za generisanje videa', + 'VideoGenSucceeded' => 'Generisanje videa uspjelo!', + 'VideoSize' => 'Velicina videa', + 'VideoWriter' => 'Video pisac', + 'Video' => 'Video', + 'ViewAll' => 'Pregledaj sve', + 'ViewEvent' => 'Pregled događaja', + 'ViewPaged' => 'Stanični pregled', + 'View' => 'Pregled', + 'V4L' => 'V4L', + 'V4LCapturesPerFrame' => 'Snimci po frejmu', + 'V4LMultiBuffer' => 'Višestr. bafer', + 'Wake' => 'Budi', + 'WarmupFrames' => 'Warmup frejmovi', + 'Watch' => 'Gledaj', + 'WebColour' => 'Web boja', + 'Web' => 'Web', + 'WebSiteUrl' => 'URL web stranice', + 'Week' => 'Sedmica', + 'WhiteBalance' => 'Balans bijele', + 'White' => 'Bijelo', + 'Wide' => 'Široko', + 'X10ActivationString' => 'X10 znakovni niz za aktiviranje', + 'X10InputAlarmString' => 'X10 ulazni znakovni niz za alarm', + 'X10OutputAlarmString' => 'X10 izlazni znakovni niz za alarm', + 'X10' => 'X10', + 'X' => 'X', + 'Yes' => 'Da', + 'YouNoPerms' => 'Nemate potrebne dozvole za pristup ovom resursu.', + 'Y' => 'Y', + 'ZoneAlarmColour' => 'Boja alarma (Red/Green/Blue)', + 'ZoneArea' => 'Oblast zone', + 'ZoneFilterSize' => 'Filter Width/Height (pixels)', + 'ZoneMinderLog' => 'ZoneMinder zapisnik', + 'ZoneMinMaxAlarmArea' => 'Min/Max alarmirana oblast', + 'ZoneMinMaxBlobArea' => 'Min/Max blob oblast', + 'ZoneMinMaxBlobs' => 'Min/Max Blobovi', + 'ZoneMinMaxFiltArea' => 'Min/Max filtrirane oblasti', + 'ZoneMinMaxPixelThres' => 'Min/Max Pixel Threshold (0-255)', + 'ZoneOverloadFrames' => 'Overload Frame Ignore Count', + 'ZoneExtendAlarmFrames' => 'Extend Alarm Frame Count', + 'Zones' => 'Zone', + 'Zone' => 'Zona', + 'ZoomIn' => 'Zoom In', + 'ZoomOut' => 'Zoom Out', + 'Zoom' => 'Zumiranje', +); + +// Complex replacements with formatting and/or placements, must be passed through sprintf +$CLANG = array( + 'CurrentLogin' => 'Prijavljeni ste kao \'%1$s\'', + 'EventCount' => '%1$s %2$s', // For example '37 Events' (from Vlang below) + 'LastEvents' => 'Last %1$s %2$s', // For example 'Last 37 Events' (from Vlang below) + 'LatestRelease' => 'Zadnja verzija servera je v%1$s, vi imate v%2$s.', + 'MonitorCount' => '%1$s %2$s', // For example '4 Monitors' (from Vlang below) + 'MonitorFunction' => 'Monitor %1$s Function', + 'RunningRecentVer' => 'Koristite najnoviju verziju Zoneminder servera, v%s.', + 'VersionMismatch' => 'Version mismatch, system is version %1$s, database is %2$s.', +); + +// The next section allows you to describe a series of word ending and counts used to +// generate the correctly conjugated forms of words depending on a count that is associated +// with that word. +// This intended to allow phrases such a '0 potatoes', '1 potato', '2 potatoes' etc to +// conjugate correctly with the associated count. +// In some languages such as English this is fairly simple and can be expressed by assigning +// a count with a singular or plural form of a word and then finding the nearest (lower) value. +// So '0' of something generally ends in 's', 1 of something is singular and has no extra +// ending and 2 or more is a plural and ends in 's' also. So to find the ending for '187' of +// something you would find the nearest lower count (2) and use that ending. +// +// So examples of this would be +// $zmVlangPotato = array( 0=>'Potatoes', 1=>'Potato', 2=>'Potatoes' ); +// $zmVlangSheep = array( 0=>'Sheep' ); +// +// where you can have as few or as many entries in the array as necessary +// If your language is similar in form to this then use the same format and choose the +// appropriate zmVlang function below. +// If however you have a language with a different format of plural endings then another +// approach is required . For instance in Russian the word endings change continuously +// depending on the last digit (or digits) of the numerator. In this case then zmVlang +// arrays could be written so that the array index just represents an arbitrary 'type' +// and the zmVlang function does the calculation about which version is appropriate. +// +// So an example in Russian might be (using English words, and made up endings as I +// don't know any Russian!!) +// 'Potato' => array( 1=>'Potati', 2=>'Potaton', 3=>'Potaten' ), +// +// and the zmVlang function decides that the first form is used for counts ending in +// 0, 5-9 or 11-19 and the second form when ending in 1 etc. +// + +// Variable arrays expressing plurality, see the zmVlang description above +$VLANG = array( + 'Event' => array( 0=>'Events', 1=>'Event', 2=>'Events' ), + 'Monitor' => array( 0=>'Monitors', 1=>'Monitor', 2=>'Monitors' ), +); +// You will need to choose or write a function that can correlate the plurality string arrays +// with variable counts. This is used to conjugate the Vlang arrays above with a number passed +// in to generate the correct noun form. +// +// In languages such as English this is fairly simple +// Note this still has to be used with printf etc to get the right formatting +function zmVlang( $langVarArray, $count ) +{ + krsort( $langVarArray ); + foreach ( $langVarArray as $key=>$value ) + { + if ( abs($count) >= $key ) + { + return( $value ); + } + } + die( 'Error, unable to correlate variable language string' ); +} + +// This is an version that could be used in the Russian example above +// The rules are that the first word form is used if the count ends in +// 0, 5-9 or 11-19. The second form is used then the count ends in 1 +// (not including 11 as above) and the third form is used when the +// count ends in 2-4, again excluding any values ending in 12-14. +// +// function zmVlang( $langVarArray, $count ) +// { +// $secondlastdigit = substr( $count, -2, 1 ); +// $lastdigit = substr( $count, -1, 1 ); +// // or +// // $secondlastdigit = ($count/10)%10; +// // $lastdigit = $count%10; +// +// // Get rid of the special cases first, the teens +// if ( $secondlastdigit == 1 && $lastdigit != 0 ) +// { +// return( $langVarArray[1] ); +// } +// switch ( $lastdigit ) +// { +// case 0 : +// case 5 : +// case 6 : +// case 7 : +// case 8 : +// case 9 : +// { +// return( $langVarArray[1] ); +// break; +// } +// case 1 : +// { +// return( $langVarArray[2] ); +// break; +// } +// case 2 : +// case 3 : +// case 4 : +// { +// return( $langVarArray[3] ); +// break; +// } +// } +// die( 'Error, unable to correlate variable language string' ); +// } + +// This is an example of how the function is used in the code which you can uncomment and +// use to test your custom function. +//$monitors = array(); +//$monitors[] = 1; // Choose any number +//echo sprintf( $CLANG['MonitorCount'], count($monitors), zmVlang( $VLANG['VlangMonitor'], count($monitors) ) ); + +// In this section you can override the default prompt and help texts for the options area +// These overrides are in the form show below where the array key represents the option name minus the initial ZM_ +// So for example, to override the help text for ZM_LANG_DEFAULT do +$OLANG = array( + 'OPTIONS_FFMPEG' => array( + 'Help' => "Parameters in this field are passed on to FFmpeg. Multiple parameters can be separated by ,~~ ". + "Examples (do not enter quotes)~~~~". + "\"allowed_media_types=video\" Set datatype to request fromcam (audio, video, data)~~~~". + "\"reorder_queue_size=nnn\" Set number of packets to buffer for handling of reordered packets~~~~". + "\"loglevel=debug\" Set verbosity of FFmpeg (quiet, panic, fatal, error, warning, info, verbose, debug)" + ), + 'OPTIONS_RTSPTrans' => array( + 'Help' => "This sets the RTSP Transport Protocol for FFmpeg.~~ ". + "TCP - Use TCP (interleaving within the RTSP control channel) as transport protocol.~~". + "UDP - Use UDP as transport protocol. Higher resolution cameras have experienced some 'smearing' while using UDP, if so try TCP~~". + "UDP Multicast - Use UDP Multicast as transport protocol~~". + "HTTP - Use HTTP tunneling as transport protocol, which is useful for passing proxies.~~" + ), + 'OPTIONS_LIBVLC' => array( + 'Help' => "Parameters in this field are passed on to libVLC. Multiple parameters can be separated by ,~~ ". + "Examples (do not enter quotes)~~~~". + "\"--rtp-client-port=nnn\" Set local port to use for rtp data~~~~". + "\"--verbose=2\" Set verbosity of libVLC" + ), + 'OPTIONS_EXIF' => array( + 'Help' => "Enable this option to embed EXIF data into each jpeg frame." + ), + 'OPTIONS_RTSPDESCRIBE' => array( + 'Help' => "Sometimes, during the initial RTSP handshake, the camera will send an updated media URL. ". + "Enable this option to tell ZoneMinder to use this URL. Disable this option to ignore the ". + "value from the camera and use the value as entered in the monitor configuration~~~~". + "Generally this should be enabled. However, there are cases where the camera can get its". + "own URL incorrect, such as when the camera is streaming through a firewall"), + 'OPTIONS_MAXFPS' => array( + 'Help' => "This field has certain limitations when used for non-local devices.~~ ". + "Failure to adhere to these limitations will cause a delay in live video, irregular frame skipping, ". + "and missed events~~". + "For streaming IP cameras, do not use this field to reduce the frame rate. Set the frame rate in the". + " camera, instead. You can, however, use a value that is slightly higher than the frame rate in the camera. ". + "In this case, this helps keep the cpu from being overtaxed in the event of a network problem.~~". + "Some, mostly older, IP cameras support snapshot mode. In this case ZoneMinder is actively polling the camera ". + "for new images. In this case, it is safe to use the field." + ), + +// 'LANG_DEFAULT' => array( +// 'Prompt' => "This is a new prompt for this option", +// 'Help' => "This is some new help for this option which will be displayed in the popup window when the ? is clicked" +// ), +); + +?> From 349693e27de728eb20270f7b405c6a6b5c58de23 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 25 Oct 2018 11:24:23 -0400 Subject: [PATCH 074/230] Add a missing comma --- scripts/zmfilter.pl.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index f9029016a..3f7e720c3 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -576,7 +576,8 @@ sub uploadArchFile { $host .= ':'.$Config{ZM_UPLOAD_PORT} if $Config{ZM_UPLOAD_PORT}; Info('Uploading to '.$host.' using SFTP'); my %sftpOptions = ( - host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} + host=>$Config{ZM_UPLOAD_HOST}, + user=>$Config{ZM_UPLOAD_USER}, ($Config{ZM_UPLOAD_PASS} ? (password=>$Config{ZM_UPLOAD_PASS}) : ()), ($Config{ZM_UPLOAD_PORT} ? (port=>$Config{ZM_UPLOAD_PORT}) : ()), ($Config{ZM_UPLOAD_TIMEOUT} ? (timeout=>$Config{ZM_UPLOAD_TIMEOUT}) : ()), From 91d83a89fa1909d27ad248e901151584823c877b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 25 Oct 2018 15:40:12 -0400 Subject: [PATCH 075/230] include semaphore function replacements --- web/includes/functions.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/web/includes/functions.php b/web/includes/functions.php index 0f49f611c..76117ad5a 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -2273,4 +2273,30 @@ function unparse_url($parsed_url, $substitutions = array() ) { $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; return "$scheme$user$pass$host$port$path$query$fragment"; } + +// The following works around php not being built with semaphore functions. +if (!function_exists('sem_get')) { + function sem_get($key) { + return fopen(__FILE__ . '.sem.' . $key, 'w+'); + } + function sem_acquire($sem_id) { + return flock($sem_id, LOCK_EX); + } + function sem_release($sem_id) { + return flock($sem_id, LOCK_UN); + } +} + +if( !function_exists('ftok') ) { + function ftok($filename = "", $proj = "") { + if ( empty($filename) || !file_exists($filename) ) { + return -1; + } else { + $filename = $filename . (string) $proj; + for($key = array(); sizeof($key) < strlen($filename); $key[] = ord(substr($filename, sizeof($key), 1))); + return dechex(array_sum($key)); + } + } +} + ?> From aaeb30a72f12524671ccd07c7ea51b26fc9bc573 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 28 Oct 2018 17:50:22 -0400 Subject: [PATCH 076/230] Move license down to perldocs at bottom. Remove unecessary line feeds and spaces --- scripts/zmstats.pl.in | 77 ++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/scripts/zmstats.pl.in b/scripts/zmstats.pl.in index 25bccfebc..410459158 100644 --- a/scripts/zmstats.pl.in +++ b/scripts/zmstats.pl.in @@ -1,39 +1,4 @@ #!/usr/bin/perl -wT -# -# ========================================================================== -# -# ZoneMinder WatchDog Script, $Date$, $Revision$ -# Copyright (C) 2001-2008 Philip Coombes -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# -# ========================================================================== - -=head1 NAME - -zmwatch.pl - ZoneMinder Stats Updating Script - -=head1 SYNOPSIS - -zmstats.pl - -=head1 DESCRIPTION - -This does background updating various stats in the db like event counts, diskspace, etc. - -=cut use strict; use bytes; @@ -66,8 +31,8 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); logSetSignal(); -Info( "Stats Daemon starting in ".START_DELAY." seconds\n" ); -sleep( START_DELAY ); +Info("Stats Daemon starting in ".START_DELAY." seconds"); +sleep(START_DELAY); my $dbh = zmDbConnect(); @@ -88,7 +53,43 @@ while( 1 ) { sleep($Config{ZM_STATS_UPDATE_INTERVAL}); } # end while (1) -Info( "Stats Daemon exiting\n" ); +Info("Stats Daemon exiting"); exit(); 1; __END__ + +# +# ========================================================================== +# +# ZoneMinder WatchDog Script, $Date$, $Revision$ +# Copyright (C) 2001-2008 Philip Coombes +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# ========================================================================== + +=head1 NAME + +zmstats.pl - ZoneMinder Stats Updating Script + +=head1 SYNOPSIS + +zmstats.pl + +=head1 DESCRIPTION + +This does background updating various stats in the db like event counts, diskspace, etc. + +=cut From 0d1f8aaf03ca01f712206463254a3932eb099ef5 Mon Sep 17 00:00:00 2001 From: Matt N Date: Sun, 28 Oct 2018 17:15:39 -0700 Subject: [PATCH 077/230] Document /api/monitors/daemonStatus/ --- docs/api.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index a6d0cc9aa..d4d4f3415 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -148,6 +148,13 @@ This API changes monitor 1 to Modect and Enabled :: curl -XPOST http://server/zm/api/monitors/1.json -d "Monitor[Function]=Modect&Monitor[Enabled]=1" + +Get Daemon Status of Monitor 1 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + curl http://server/zm/api/monitors/daemonStatus/id:1/daemon:zmc.json Add a monitor ^^^^^^^^^^^^^^ From 95a6d0666a55f2e734658b3ee2db9496661bead8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Oct 2018 09:59:26 -0400 Subject: [PATCH 078/230] Improve behaviour and reduce extra logging when db goes away --- web/includes/Event.php | 8 +++--- web/includes/functions.php | 31 ++++++++++++------------ web/skins/classic/includes/functions.php | 2 +- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/web/includes/Event.php b/web/includes/Event.php index 1852bb8b0..fb7af931e 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -546,9 +546,11 @@ class Event { } $filters = array(); $result = dbQuery($sql, $values); - $results = $result->fetchALL(); - foreach ( $results as $row ) { - $filters[] = new Event($row); + if ( $result ) { + $results = $result->fetchALL(); + foreach ( $results as $row ) { + $filters[] = new Event($row); + } } return $filters; } diff --git a/web/includes/functions.php b/web/includes/functions.php index 76117ad5a..29f627722 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -1909,23 +1909,24 @@ function logState() { # This is an expensive request, as it has to hit every row of the Logs Table $sql = 'SELECT Level, COUNT(Level) AS LevelCount FROM Logs WHERE Level < '.Logger::INFO.' AND TimeKey > unix_timestamp(now() - interval '.ZM_LOG_CHECK_PERIOD.' second) GROUP BY Level ORDER BY Level ASC'; - $counts = dbFetchAll( $sql ); - - foreach ( $counts as $count ) { - if ( $count['Level'] <= Logger::PANIC ) - $count['Level'] = Logger::FATAL; - if ( !($levelCount = $levelCounts[$count['Level']]) ) { - Error( "Unexpected Log level ".$count['Level'] ); - next; - } - if ( $levelCount[1] && $count['LevelCount'] >= $levelCount[1] ) { - $state = 'alarm'; - break; - } elseif ( $levelCount[0] && $count['LevelCount'] >= $levelCount[0] ) { - $state = 'alert'; + $counts = dbFetchAll($sql); + if ( $counts ) { + foreach ( $counts as $count ) { + if ( $count['Level'] <= Logger::PANIC ) + $count['Level'] = Logger::FATAL; + if ( !($levelCount = $levelCounts[$count['Level']]) ) { + Error('Unexpected Log level '.$count['Level']); + next; + } + if ( $levelCount[1] && $count['LevelCount'] >= $levelCount[1] ) { + $state = 'alarm'; + break; + } elseif ( $levelCount[0] && $count['LevelCount'] >= $levelCount[0] ) { + $state = 'alert'; + } } } - return( $state ); + return $state; } function isVector ( &$array ) { diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index ceb12d621..7847e37c7 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -345,7 +345,7 @@ if ($reload == 'reload') ob_start(); 90 ? ' class="warning"' : '' ).'>'.translate('DB').':'.$connections.'/'.$max_connections.''; ?>
  • : From 9a2d58adceaa6b1335e48290dda64770812033f7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Oct 2018 11:03:03 -0400 Subject: [PATCH 079/230] We don't store all the permissions in the session anymore. We just use the global user object --- web/api/app/Controller/MonitorsController.php | 6 ++++-- web/api/app/Controller/StatesController.php | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index 5ce4bb476..185a06c84 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -207,8 +207,10 @@ class MonitorsController extends AppController { if ( !$this->Monitor->exists() ) { throw new NotFoundException(__('Invalid monitor')); } - if ( $this->Session->Read('systemPermission') != 'Edit' ) { - throw new UnauthorizedException(__('Insufficient privileges')); + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if ( !$canEdit ) { + throw new UnauthorizedException(__('Insufficient privileges')); return; } $this->request->allowMethod('post', 'delete'); diff --git a/web/api/app/Controller/StatesController.php b/web/api/app/Controller/StatesController.php index 29201d2c1..b96efe0aa 100644 --- a/web/api/app/Controller/StatesController.php +++ b/web/api/app/Controller/StatesController.php @@ -59,8 +59,9 @@ public function add() { if ($this->request->is('post')) { - if ($this->Session->Read('systemPermission') != 'Edit') - { + global $user; + $canEdit = (!$user) || ($user['System'] == 'Edit'); + if ( !$canEdit ) { throw new UnauthorizedException(__('Insufficient privileges')); return; } From 6691b5fb52b09f4dfcedd4b7bac47d421c991b1e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Oct 2018 12:50:50 -0400 Subject: [PATCH 080/230] Include CORS headers when there is a Server defined, instead of requiring there to be more than 1 --- web/includes/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/includes/functions.php b/web/includes/functions.php index 29f627722..7fe174470 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -41,7 +41,7 @@ function CORSHeaders() { # The following is left for future reference/use. $valid = false; $Servers = Server::find(); - if ( sizeof($Servers) <= 1 ) { + if ( sizeof($Servers) < 1 ) { # Only need CORSHeaders in the event that there are multiple servers in use. # ICON: Might not be true. multi-port? return; From 39061038fb331369c12e984a08011a9fc7933a67 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Oct 2018 14:40:05 -0400 Subject: [PATCH 081/230] Don't include related models in Storage index --- web/api/app/Controller/StorageController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/api/app/Controller/StorageController.php b/web/api/app/Controller/StorageController.php index 325dea4dd..16791788c 100644 --- a/web/api/app/Controller/StorageController.php +++ b/web/api/app/Controller/StorageController.php @@ -31,7 +31,7 @@ class StorageController extends AppController { * @return void */ public function index() { - $this->Storage->recursive = 0; + $this->Storage->recursive = -1; $options = ''; $storage_areas = $this->Storage->find('all',$options); From f95379742b325bfc5d9d8c28df46068a7b8400f3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 30 Oct 2018 12:04:05 -0400 Subject: [PATCH 082/230] Use a warning colour when motion detection is disabled. --- web/skins/classic/css/base/skin.css | 12 ++++++------ web/skins/classic/views/console.php | 20 ++++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/web/skins/classic/css/base/skin.css b/web/skins/classic/css/base/skin.css index 3fdd5a12f..f95c0003e 100644 --- a/web/skins/classic/css/base/skin.css +++ b/web/skins/classic/css/base/skin.css @@ -341,17 +341,17 @@ fieldset > legend { /* * Behavior classes */ -.alarm, .errorText, .error { - color: #ff3f34; -} -.alert, .warnText, .warning { - color: #ffa801; -} .ok, .infoText { color: #0fb9b1; } +.alert, .warnText, .warning, .disabledText { + color: #ffa801; +} +.alarm, .errorText, .error { + color: #ff3f34; +} .fakelink { color: #7f7fb2; diff --git a/web/skins/classic/views/console.php b/web/skins/classic/views/console.php index 46575ffa0..6de978a6c 100644 --- a/web/skins/classic/views/console.php +++ b/web/skins/classic/views/console.php @@ -251,15 +251,19 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) { } } if ( $monitor['Function'] == 'None' ) - $fclass = 'errorText'; + $function_class = 'errorText'; else - $fclass = 'infoText'; - if ( !$monitor['Enabled'] ) - $fclass .= ' disabledText'; + $function_class = 'infoText'; + + $scale = max(reScale(SCALE_BASE, $monitor['DefaultScale'], ZM_WEB_DEFAULT_SCALE), SCALE_BASE); $stream_available = canView('Stream') and $monitor['Type']=='WebSite' or ($monitor['CaptureFPS'] && $monitor['Function'] != 'None'); - $dot_class=$source_class; - if ( $fclass != 'infoText' ) $dot_class=$fclass; + $dot_class = $source_class; + if ( $function_class != 'infoText' ) { + $dot_class = $function_class; + } else if ( !$monitor['Enabled'] ) { + $dot_class .= ' warnText'; + } if ( ZM_WEB_ID_ON_CONSOLE ) { ?> @@ -268,7 +272,7 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) { } ?> - ' : '>') . $monitor['Name'] ?>
    + ' : '>') . $monitor['Name'] ?>
    ', array_map(function($group_id){ $Group = Group::find_one(array('Id'=>$group_id)); @@ -281,7 +285,7 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) { ?>
    - '.translate('Fn'.$monitor['Function']).( empty($monitor['Enabled']) ? ', disabled' : '' ) .'', canEdit( 'Monitors' ) ) ?>
    + '.translate('Fn'.$monitor['Function']).( empty($monitor['Enabled']) ? ', disabled' : '' ) .'', canEdit('Monitors') ) ?>

    Date: Tue, 30 Oct 2018 18:35:50 -0400 Subject: [PATCH 083/230] Fixes for a couple issues in the Ubuntu guide (#2285) * Xenial needs the ppa:iconnor/zoneminder repo Otherwise, trying to install the zoneminder package fails with an unmet dependency on php-apc. See https://forums.zoneminder.com/viewtopic.php?t=27638 for details. * Add necessary modules before configuring Apache When you add the zoneminder configuration before enabling mod_rewrite, subsequent commands (like the one to enable mod_cgi) give you an error like: AH00526: Syntax error on line 37 of /etc/apache2/conf-enabled/zoneminder.conf: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration --- docs/installationguide/ubuntu.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installationguide/ubuntu.rst b/docs/installationguide/ubuntu.rst index 3df065264..6c686d7ed 100644 --- a/docs/installationguide/ubuntu.rst +++ b/docs/installationguide/ubuntu.rst @@ -49,7 +49,7 @@ guide you with a quick search. add-apt-repository ppa:iconnor/zoneminder-1.32 - If you are on trusty, you may want to add both, as there are some packages for dependencies included in the old ppa. + If you are on Trusty or Xenial, you may want to add both, as there are some packages for dependencies included in the old ppa. Update repo and upgrade. @@ -138,9 +138,9 @@ Set /etc/zm/zm.conf to root:www-data 740 and www-data access to content :: - a2enconf zoneminder a2enmod cgi a2enmod rewrite + a2enconf zoneminder You may also want to enable to following modules to improve caching performance From 2b0df3e4e2c9b064088a02eac6d5ce190e9d0561 Mon Sep 17 00:00:00 2001 From: ratmole Date: Wed, 31 Oct 2018 10:17:36 +0200 Subject: [PATCH 084/230] API - Disable E_NOTICE from php error reporting in cake debug Using zmNinja, the API reports E_NOTICE errors Notice (8): compact(): Undefined variable: subject [CORE/Cake/Utility/ObjectCollection.php, line 128] Notice (8): compact() [function.compact]: Undefined variable: subject [CORE/Cake/Utility/ObjectCollection.php, line 128] Notice (8): compact() [function.compact]: Undefined variable: subject [CORE/Cake/Utility/ObjectCollection.php, line 128] Notice (8): compact() [function.compact]: Undefined variable: subject [CORE/Cake/Utility/ObjectCollection.php, line 128] and zmNinja will not work... there is a better way, but i think disabling E_NOTICE error is way easier see: https://github.com/ZoneMinder/zoneminder/pull/2269 --- web/api/app/Config/core.php.default | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/api/app/Config/core.php.default b/web/api/app/Config/core.php.default index 39a51690c..64f439420 100644 --- a/web/api/app/Config/core.php.default +++ b/web/api/app/Config/core.php.default @@ -50,7 +50,7 @@ */ Configure::write('Error', array( 'handler' => 'ErrorHandler::handleError', - 'level' => E_ALL & ~E_DEPRECATED, + 'level' => E_ALL & ~E_DEPRECATED & ~E_NOTICE, 'trace' => true )); From e87ded35f1b7f530abb013bcf351431ad4a03a92 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 31 Oct 2018 11:08:44 -0400 Subject: [PATCH 085/230] rough in adding Monitor_Status to Monitors --- web/api/app/Model/Monitor.php | 9 ++++- web/api/app/Model/Monitor_Status.php | 59 ++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 web/api/app/Model/Monitor_Status.php diff --git a/web/api/app/Model/Monitor.php b/web/api/app/Model/Monitor.php index 5c0f62641..2e6584794 100644 --- a/web/api/app/Model/Monitor.php +++ b/web/api/app/Model/Monitor.php @@ -116,8 +116,15 @@ class Monitor extends AppModel { 'OutputCodec' => array('h264','mjpeg','mpeg1','mpeg2'), 'OutputContainer' => array('auto','mp4','mkv'), 'DefaultView' => array('Events','Control'), - 'Status' => array('Unknown','NotRunning','Running','NoSignal','Signal'), + #'Status' => array('Unknown','NotRunning','Running','NoSignal','Signal'), ) ); + public $hasOne = array( + 'Monitor_Status' => array( + 'className' => 'Monitor_Status', + 'foreignKey' => 'MonitorId', + 'joinTable' => 'Monitor_Status', + ) + ); } diff --git a/web/api/app/Model/Monitor_Status.php b/web/api/app/Model/Monitor_Status.php new file mode 100644 index 000000000..40f4aa2c2 --- /dev/null +++ b/web/api/app/Model/Monitor_Status.php @@ -0,0 +1,59 @@ + array( + 'numeric' => array( + 'rule' => array('numeric'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + ); + + public $actsAs = array( + 'CakePHP-Enum-Behavior.Enum' => array( + 'Status' => array('Unknown','NotRunning','Running','NoSignal','Signal'), + ) + ); + + //The Associations below have been created with all possible keys, those that are not needed can be removed +} From 69f7d367296d16e11f3d6833bdbb6cca84444704 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 31 Oct 2018 11:34:30 -0400 Subject: [PATCH 086/230] Make it clear that audio recording is only for ffmpeg input type --- web/skins/classic/views/monitor.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index fa2ebd44f..979d4f34e 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -937,7 +937,15 @@ if ( $monitor->Type() == 'Local' ) { ?> - RecordAudio() ) { ?> checked="checked"/> + +Type() == 'Ffmpeg' ) { ?> + RecordAudio() ) { ?> checked="checked"/> + + Audio recording only available with FFMPEG + + + + Date: Wed, 31 Oct 2018 11:35:06 -0400 Subject: [PATCH 087/230] add further note about needing h264 passthrough --- web/skins/classic/views/monitor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 979d4f34e..5142e4a96 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -941,7 +941,7 @@ if ( $monitor->Type() == 'Local' ) { Type() == 'Ffmpeg' ) { ?> RecordAudio() ) { ?> checked="checked"/> - Audio recording only available with FFMPEG + Audio recording only available with FFMPEG using H264 Passthrough From bdb50567df2787e52bca8bbb5f306a71c6871263 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 31 Oct 2018 11:56:08 -0400 Subject: [PATCH 088/230] fix disk_event_space to event_disk_space --- web/includes/Storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/includes/Storage.php b/web/includes/Storage.php index 0744889f9..95f1dab84 100644 --- a/web/includes/Storage.php +++ b/web/includes/Storage.php @@ -189,7 +189,7 @@ class Storage { # This isn't a function like this in php, so we have to add up the space used in each event. if ( ( !array_key_exists('disk_used_space', $this)) or !$this->{'disk_used_space'} ) { if ( $this->{'Type'} == 's3fs' ) { - $this->{'disk_used_space'} = $this->disk_event_space(); + $this->{'disk_used_space'} = $this->event_disk_space(); } else { $path = $this->Path(); if ( file_exists($path) ) { From 37b2da59ce7047b1d6d57bd1e63a6f4824e24232 Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Thu, 1 Nov 2018 13:30:08 -0400 Subject: [PATCH 089/230] added streaming interface docs --- docs/api.rst | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index d4d4f3415..109569492 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -5,7 +5,6 @@ This document will provide an overview of ZoneMinder's API. This is work in prog Overview ^^^^^^^^ - In an effort to further 'open up' ZoneMinder, an API was needed. This will allow quick integration with and development of ZoneMinder. @@ -13,6 +12,78 @@ The API is built in CakePHP and lives under the ``/api`` directory. It provides a RESTful service and supports CRUD (create, retrieve, update, delete) functions for Monitors, Events, Frames, Zones and Config. +Streaming Interface +^^^^^^^^^^^^^^^^^^^ +Developers working on their application often ask if there is an "API" to receive live streams, or recorded event streams. +It is possible to stream both live and recorded streams. This isn't strictly an "API" per-se (that is, it is not integrated +into the Cake PHP based API layer discussed here) and also why we've used the term "Interface" instead of an "API". + +Live Streams +============= +What you need to know is that if you want to display "live streams", ZoneMinder sends you streaming JPEG images (MJPEG) +which can easily be rendered in a browser using an ``img src`` tag. + +For example: + +:: + + + +will display a live feed from monitor id 1, scaled down by 50% in quality and resized to 640x480px. + +* This assumes ``/zm/cgi-bin`` is your CGI_BIN path. Change it to what is correct in your system +* The "auth" token you see above is required if you use ZoneMinder authentication. To understand how to get the auth token, please read the "Login, Logout & API security" section below. +* The "connkey" parameter is essentially a random number which uniquely identifies a stream. If you don't specify a connkey, ZM will generate its own. It is recommended to generate a connkey because you can then use it to "control" the stream (pause/resume etc.) +* Instead of dealing with the "auth" token, you can also use ``&user=username&pass=password`` where "username" and "password" are your ZoneMinder username and password respectively. Note that this is not recommended because you are transmitting them in a URL and even if you use HTTPS, they may show up in web server logs. + + +PTZ on live streams +------------------- +PTZ commands are pretty cryptic in ZoneMinder. This is not meant to be an exhaustive guide, but just something to whet your appetite: + + +Lets assume you have a monitor, with ID=6. Let's further assume you want to pan it left. + +You'd need to send a: +``POST`` command to ``https://yourserver/zm/index.php`` with the following data payload in the command (NOT in the URL) + +``view=request&request=control&id=6&control=moveConLeft&xge=30&yge=30`` + +Obviously, if you are using authentication, you need to be logged in for this to work. + +Like I said, at this stage, this is only meant to get you started. Explore the ZoneMinder code and use "Inspect source" as you use PTZ commands in the ZoneMinder source code. +`control_functions.php `__ is a great place to start. + + +Pre-recorded (past event) streams +================================= + +Similar to live playback, if you have chosen to store events in JPEG mode, you can play it back using: + +:: + + + + +* This assumes ``/zm/cgi-bin`` is your CGI_BIN path. Change it to what is correct in your system +* This will playback event 293820, starting from frame 1 as an MJPEG stream +* Like before, you can add more parameters like ``scale`` etc. +* auth and connkey have the same meaning as before, and yes, you can replace auth by ``&user=usename&pass=password`` as before and the same security concerns cited above apply. + +If instead, you have chosen to use the MP4 (Video) storage mode for events, you can directly play back the saved video file: + +:: + + + +* This will play back the video recording for event 294690 + +What other parameters are supported? +===================================== +The best way to answer this question is to play with ZoneMinder console. Open a browser, play back live or recorded feed, and do an "Inspect Source" to see what parameters +are generated. Change and observe. + + Enabling API ^^^^^^^^^^^^ A default ZoneMinder installs with APIs enabled. You can explictly enable/disable the APIs @@ -396,6 +467,8 @@ PTZ Control APIs PTZ controls associated with a monitor are stored in the Controls table and not the Monitors table inside ZM. What that means is when you get the details of a Monitor, you will only know if it is controllable (isControllable:true) and the control ID. To be able to retrieve PTZ information related to that Control ID, you need to use the controls API +Note that these APIs only retrieve control data related to PTZ. They don't actually move the camera. + This returns all the control definitions: :: From 2953ff4db4afa15266917d9deffe28375108811e Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Thu, 1 Nov 2018 13:40:07 -0400 Subject: [PATCH 090/230] header level fix --- docs/api.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 109569492..a06797cd1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -19,7 +19,7 @@ It is possible to stream both live and recorded streams. This isn't strictly an into the Cake PHP based API layer discussed here) and also why we've used the term "Interface" instead of an "API". Live Streams -============= +~~~~~~~~~~~~~~ What you need to know is that if you want to display "live streams", ZoneMinder sends you streaming JPEG images (MJPEG) which can easily be rendered in a browser using an ``img src`` tag. @@ -56,7 +56,7 @@ Like I said, at this stage, this is only meant to get you started. Explore the Z Pre-recorded (past event) streams -================================= +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Similar to live playback, if you have chosen to store events in JPEG mode, you can play it back using: @@ -79,7 +79,7 @@ If instead, you have chosen to use the MP4 (Video) storage mode for events, you * This will play back the video recording for event 294690 What other parameters are supported? -===================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The best way to answer this question is to play with ZoneMinder console. Open a browser, play back live or recorded feed, and do an "Inspect Source" to see what parameters are generated. Change and observe. @@ -490,3 +490,4 @@ ZM APIs have various APIs that help you in determining host (aka ZM) daemon stat curl -XGET http://server/zm/api/host/getLoad.json # returns current load of ZM curl -XGET http://server/zm/api/host/getDiskPercent.json # returns in GB (not percentage), disk usage per monitor (that is, space taken to store various event related information,images etc. per monitor) + From cd8d609e846eafa87b86e42fb59a4edf2ba1339c Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Thu, 1 Nov 2018 14:00:09 -0400 Subject: [PATCH 091/230] initial multi-server/storage changes --- docs/api.rst | 101 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index a06797cd1..d70c7eabc 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -142,7 +142,21 @@ using CuRL like so: curl -b cookies.txt http://yourzmip/zm/api/monitors.json -This would return a list of monitors and pass on the authentication information to the ZM API layer. +This would return a list of monitors and pass on the authentication information to the ZM API layer. It is worthwhile noting, that starting ZM 1.32.3 and beyond, this API also returns a ``Monitor_Status`` object per monitor. It looks like this: + +:: + + "Monitor_Status": { + "MonitorId": "2", + "Status": "Connected", + "CaptureFPS": "1.67", + "AnalysisFPS": "1.67", + "CaptureBandwidth": "52095" + } + + +If you don't see this in your API, you are running an older version of ZM. This gives you a very convenient way to check monitor status without calling the ``daemonCheck`` API described later. + A deeper dive into the login process ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -486,8 +500,89 @@ ZM APIs have various APIs that help you in determining host (aka ZM) daemon stat :: - curl -XGET http://server/zm/api/host/daemonCheck.json # 1 = ZM running 0=not running curl -XGET http://server/zm/api/host/getLoad.json # returns current load of ZM - curl -XGET http://server/zm/api/host/getDiskPercent.json # returns in GB (not percentage), disk usage per monitor (that is, space taken to store various event related information,images etc. per monitor) + + # Note that ZM 1.32.3 onwards has the same information in Monitors.json which is more reliable and works for multi-server too. + curl -XGET http://server/zm/api/host/daemonCheck.json # 1 = ZM running 0=not running + + # The API below uses "du" to calculate disk space. We no longer recommend you use it if you have many events. Use the Storage APIs instead, described later + curl -XGET http://server/zm/api/host/getDiskPercent.json # returns in GB (not percentage), disk usage per monitor (that is,space taken to store various event related information,images etc. per monitor) + + +Storage and Server APIs +^^^^^^^^^^^^^^^^^^^^^^^ + +ZoneMinder introduced many new options that allowed you to configure multiserver/multistorage configurations. While a part of this was available in previous versions, a lot of rework was done as part of ZM 1.31 and 1.32. As part of that work, a lot of new and useful APIs were added. Some of these are part of ZM 1.32 and others will be part of ZM 1.32.3 (of course, if you build from master, you can access them right away, or wait till a stable release is out. + + + +This returns storage data for my single server install. If you are using multi-storage, you'll see many such "Storage" entries, one for each storage defined: + +:: + + curl http://server/zm/api/storage.json + +Returns: + +:: + + { + "storage": [ + { + "Storage": { + "Id": "0", + "Path": "\/var\/cache\/zoneminder\/events", + "Name": "Default", + "Type": "local", + "Url": null, + "DiskSpace": "364705447651", + "Scheme": "Medium", + "ServerId": null, + "DoDelete": true + } + } + ] + } + + + +"DiskSpace" is the disk used in bytes. While this doesn't return disk space data as rich as ``/host/getDiskPercent``, it is much more efficient. + +Similarly, + +:: + curl http://server/zm/api/server.json + +Returns: + +:: + + { + "servers": [ + { + "Server": { + "Id": "1", + "Name": "server1", + "Hostname": "sserver1.mydomain.com", + "State_Id": null, + "Status": "Running", + "CpuLoad": "0.9", + "TotalMem": "6186237952", + "FreeMem": "156102656", + "TotalSwap": "536866816", + "FreeSwap": "525697024", + "zmstats": false, + "zmaudit": false, + "zmtrigger": false + } + } + ] + } + +This only works if you have a multiserver setup in place. If you don't it will return an empty array. + + + + From 46cd2fc3ffb387762c8fa378cee96d117ea07ce6 Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Thu, 1 Nov 2018 14:04:51 -0400 Subject: [PATCH 092/230] relocate monitor_status to the right place --- docs/api.rst | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index d70c7eabc..ec4fe8d55 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -142,21 +142,7 @@ using CuRL like so: curl -b cookies.txt http://yourzmip/zm/api/monitors.json -This would return a list of monitors and pass on the authentication information to the ZM API layer. It is worthwhile noting, that starting ZM 1.32.3 and beyond, this API also returns a ``Monitor_Status`` object per monitor. It looks like this: - -:: - - "Monitor_Status": { - "MonitorId": "2", - "Status": "Connected", - "CaptureFPS": "1.67", - "AnalysisFPS": "1.67", - "CaptureBandwidth": "52095" - } - - -If you don't see this in your API, you are running an older version of ZM. This gives you a very convenient way to check monitor status without calling the ``daemonCheck`` API described later. - +This would return a list of monitors and pass on the authentication information to the ZM API layer. A deeper dive into the login process ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -217,6 +203,22 @@ Return a list of all monitors curl http://server/zm/api/monitors.json +It is worthwhile to note that starting ZM 1.32.3 and beyond, this API also returns a ``Monitor_Status`` object per monitor. It looks like this: + +:: + + "Monitor_Status": { + "MonitorId": "2", + "Status": "Connected", + "CaptureFPS": "1.67", + "AnalysisFPS": "1.67", + "CaptureBandwidth": "52095" + } + + +If you don't see this in your API, you are running an older version of ZM. This gives you a very convenient way to check monitor status without calling the ``daemonCheck`` API described later. + + Retrieve monitor 1 ^^^^^^^^^^^^^^^^^^^ @@ -551,6 +553,7 @@ Returns: Similarly, :: + curl http://server/zm/api/server.json Returns: From 536d9226e0575ecc34a6f324cd2180b69d823fd8 Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Thu, 1 Nov 2018 14:07:53 -0400 Subject: [PATCH 093/230] ptz - clarify this is meta-data --- docs/api.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index ec4fe8d55..647eedac0 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -478,12 +478,12 @@ Create a Zone &Zone[MaxBlobs]=\ &Zone[OverloadFrames]=0" -PTZ Control APIs -^^^^^^^^^^^^^^^^ +PTZ Control Meta-Data APIs +^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTZ controls associated with a monitor are stored in the Controls table and not the Monitors table inside ZM. What that means is when you get the details of a Monitor, you will only know if it is controllable (isControllable:true) and the control ID. To be able to retrieve PTZ information related to that Control ID, you need to use the controls API -Note that these APIs only retrieve control data related to PTZ. They don't actually move the camera. +Note that these APIs only retrieve control data related to PTZ. They don't actually move the camera. See the "PTZ on live streams" section to move the camera. This returns all the control definitions: :: From 14c30eac7682debb92dcc9633755d91bb61dba5c Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Thu, 1 Nov 2018 14:08:09 -0400 Subject: [PATCH 094/230] typo --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 647eedac0..e102851a7 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -566,7 +566,7 @@ Returns: "Server": { "Id": "1", "Name": "server1", - "Hostname": "sserver1.mydomain.com", + "Hostname": "server1.mydomain.com", "State_Id": null, "Status": "Running", "CpuLoad": "0.9", From 86a086c216647dc37b503a47be61cb97ea26a794 Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Thu, 1 Nov 2018 14:13:22 -0400 Subject: [PATCH 095/230] server json api typo --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index e102851a7..2c7ad1d16 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -554,7 +554,7 @@ Similarly, :: - curl http://server/zm/api/server.json + curl http://server/zm/api/servers.json Returns: From ace1134df137c1bb7bea35156158d57eafe50b0b Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Thu, 1 Nov 2018 14:40:31 -0400 Subject: [PATCH 096/230] further reading --- docs/api.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 2c7ad1d16..2f90b7fdf 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -585,7 +585,14 @@ Returns: This only works if you have a multiserver setup in place. If you don't it will return an empty array. - +Further Reading +^^^^^^^^^^^^^^^^ +As described earlier, treat this document as an "introduction" to the important parts of the API and streaming interfaces. +There are several details that haven't yet been documented. Till they are, here are some resources: + +* zmNinja, the open source mobile app for ZoneMinder is 100% based on ZM APIs. Explore its `source code `__ to see how things work. +* Launch up ZM console in a browser, and do an "Inspect source". See how images are being rendered. Go to the networks tab of the inspect source console and look at network requests that are made when you pause/play/forward streams. +* If you still can't find an answer, post your question in the `forums `__ (not the github repo). From 4b24bf4e36f9b0aa265c89b62dea12c11ec88890 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 2 Nov 2018 11:48:35 -0400 Subject: [PATCH 097/230] merge from storageareas, fully specify Module for zmDbDisconnect as we havn't 'used' it --- scripts/ZoneMinder/lib/ZoneMinder/Logger.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index 3caa6af4a..6d7ecc660 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -705,7 +705,7 @@ sub Fatal( @ ) { $SIG{TERM}(); } # I think if we don't disconnect we will leave sockets around in TIME_WAIT - zmDbDisconnect(); + ZoneMinder::Database::zmDbDisconnect(); exit(-1); } From 3aee902a96daab320f6a1c17c81664d997916d75 Mon Sep 17 00:00:00 2001 From: Andy Bauer Date: Sun, 4 Nov 2018 17:11:19 -0600 Subject: [PATCH 098/230] update nginx support on redhat --- distros/redhat/nginx/README.Fedora | 168 +++++++++++------- distros/redhat/nginx/zoneminder.conf.in | 4 + .../redhat/nginx/zoneminder.php-fpm.conf.in | 12 +- distros/redhat/nginx/zoneminder.tmpfiles.in | 3 + distros/redhat/zoneminder.spec | 3 +- 5 files changed, 118 insertions(+), 72 deletions(-) diff --git a/distros/redhat/nginx/README.Fedora b/distros/redhat/nginx/README.Fedora index 0a5168231..013a502b0 100644 --- a/distros/redhat/nginx/README.Fedora +++ b/distros/redhat/nginx/README.Fedora @@ -1,39 +1,34 @@ What's New ========== -1. This is an *experimental* build of zoneminder which uses the - nginx web server. +1. See the ZoneMinder release notes for a list of new features: + https://github.com/ZoneMinder/zoneminder/releases -2. The Apache ScriptAlias has been changed from "/cgi-bin/zm/zms" to - "/cgi-bin-zm/zms". This has been to done to avoid this bug: - https://bugzilla.redhat.com/show_bug.cgi?id=973067 +2. The contents of the ZoneMinder Apache config file have changed. In + addition, this ZoneMinder package now requires you to manually symlink the + ZoneMinder Apache config file. See new install step 6 and upgrade step 3 + below for details. - IMPORTANT: You must manually inspect the value for PATH_ZMS under Options - and verify it is set to "/cgi-bin-zm/nph-zms". Failure to do so will result - in a broken system. You have been warned. - -3. Due to the active state of the ZoneMinder project, we now recommend granting - ALL permission to the ZoneMinder mysql account. This change must be done - manually before ZoneMinder will run. See the installation steps below. - -4. This package uses the HTTPS protocol by default to access the web portal. - Requests using HTTP will auto-redirect to HTTPS. See README.https for - more information. - -5. This package ships with the new ZoneMinder API enabled. +3. This is an experimental build of ZoneMinder supporting nginx, rather than + apache web server. +4. If you have installed ZoneMinder from the FedBerry repositories, this build + of ZoneMinder has support for Raspberry Pi hardware acceleration when using + ffmpeg. Unforunately, there is a problem with the same hardware acceleration + when using libvlc. Consequently, libvlc support in thie build of ZoneMinder + has been disabled until the problem is resolved. See the following bug + report for details: https://trac.videolan.org/vlc/ticket/18594 + New installs ============ -1. This package supports either community-mysql-server or mariadb-server with - mariadb being the preferred choice. Unless you are already using MariaDB or - Mysql server, you need to ensure that the server is configured to start - during boot and properly secured by running: +1. Unless you are already using MariaDB server, you need to ensure that the + server is configured to start during boot and properly secured by running: - sudo dnf install mariadb-server - sudo systemctl enable mariadb - sudo systemctl start mariadb.service - mysql_secure_installation + sudo dnf install mariadb-server + sudo systemctl enable mariadb + sudo systemctl start mariadb.service + mysql_secure_installation 2. Assuming the database is local and using the password for the root account set during the previous step, you will need to create the ZoneMinder @@ -48,13 +43,17 @@ New installs anything that suits your environment. 3. If you have chosen to change the zoneminder database account credentials to - something other than zmuser/zmpass, you must now edit /etc/zm/zm.conf. - Change ZM_DB_USER and ZM_DB_PASS to the values you created in the previous - step. + something other than zmuser/zmpass, you must now create a config file under + /etc/zm/conf.d and set your credentials there. For example, create the file + /etc/zm/conf.d/zm-db-user.conf and add the following content to it: + + ZM_DB_USER = {username of the sql account you want to use} + ZM_DB_PASS = {password of the sql account you want to use} - This version of zoneminder no longer requires you to make a similar change - to the credentials in /usr/share/zoneminder/www/api/app/Config/database.php - This now happens dynamically. Do *not* make any changes to this file. + Once the file has been saved, set proper file & ownership permissions on it: + + sudo chown root:apache *.conf + sudo chmod 640 *.conf 4. Edit /etc/php.ini, uncomment the date.timezone line, and add your local timezone. PHP will complain loudly if this is not set, or if it is set @@ -80,54 +79,87 @@ New installs SELINUX line from "enforcing" to "disabled". This change will take effect after a reboot. -6. This package comes preconfigured for HTTPS using the default self signed - certificate on your system. We recommend you keep this configuration. +6. Configure the web server - If this does not meet your needs, then read README.https to - learn about alternatives. + This package uses the HTTPS protocol by default to access the web portal, + using the default self signed certificate on your system. Requests using + HTTP will auto-redirect to HTTPS. -7. Edit /etc/sysconfig/fcgiwrap and set DAEMON_PROCS to the maximum number of + Inspect the web server configuration file and verify it meets your needs: + + /etc/zm/www/zoneminder.conf + + If you are running other web enabled services then you may need to edit + this file to suite. See README.https to learn about other alternatives. + + When in doubt, proceed with the default: + + sudo ln -s /etc/zm/www/zoneminder.conf /etc/nginx/default.d/ + +7. Fcgiwrap is required when using ZoneMinder with Nginx. At the time of this + writing, fcgiwrap is not yet available in the Fedora repos. Until it + becomes available, you may install it from my Copr repository: + + https://copr.fedorainfracloud.org/coprs/kni/fcgiwrap/ + + Follow the intructions on that site to enable the repo. Once enabled, + install fcgiwrap: + + sudo dnf install fcgiwrap + + After fcgiwrap is installed, it must be configured. Edit + /etc/sysconfig/fcgiwrap and set DAEMON_PROCS to the maximum number of simulatneous streams the server should support. Generally, a good minimum value for this equals the total number of cameras you expect to view at the same time. 8. Now start the web server: - sudo systemctl enable nginx - sudo systemctl start nginx + sudo systemctl enable nginx + sudo systemctl start nginx 9. Now start zoneminder: - sudo systemctl enable zoneminder - sudo systemctl start zoneminder + sudo systemctl enable zoneminder + sudo systemctl start zoneminder -10.The Fedora repos have a ZoneMinder package available, but it does not - support ffmpeg or libvlc, which many modern IP cameras require. Most users - will want to prevent the ZoneMinder package in the Fedora repos from - overwriting the ZoneMinder package in zmrepo, during a future dnf update. To - prevent that from happening you must edit /etc/yum.repos.d/fedora.repo - and /etc/yum.repos.d/fedora-updates.repo. Add the line "exclude=zoneminder*" - without the quotes under the [fedora] and [fedora-updates] blocks, - respectively. +10. Optionally configure the firewall + + All Redhat distros ship with the firewall enabled. That means you will not + be able to access the ZoneMinder web console from a remote machine until + changes are made to the firewall. + + What follows are a set of minimal commands to allow remote access to the + ZoneMinder web console and also allow ZoneMinder's ONVIF discovery to + work. The following commands do not put any restrictions on which remote + machine(s) have access to the listed ports or services. + + sudo firewall-cmd --permanent --zone=public --add-service=http + sudo firewall-cmd --permanent --zone=public --add-service=https + sudo firewall-cmd --permanent --zone=public --add-port=3702/udp + sudo firewall-cmd --reload + + Additional changes to the firewall may be required, depending on your + security requirements and how you use the system. It is up to you to verify + these commands are sufficient. + +11. Access the ZoneMinder web console + + You may now access the ZoneMinder web console from your web browser using + an appropriate url. Here are some examples: + + http://localhost/zm (works from the local machine only) + http://{machine name}/zm (works only if dns is configured for your network) + http://{ip address}/zm Upgrades ======== -1. Verify /etc/zm/zm.conf. - - If zm.conf was manually edited before running the upgrade, the installation - may not overwrite it. In this case, it will create the file - /etc/zm/zm.conf.rpmnew. - - For example, this will happen if you are using database account credentials - other than zmuser/zmpass. - - Compare /etc/zm/zm.conf to /etc/zm/zm.conf.rpmnew. Verify that zm.conf - contains any new config settings that may be in zm.conf.rpmnew. - - This version of zoneminder no longer requires you to make a similar change - to the credentials in /usr/share/zoneminder/www/api/app/Config/database.php - This now happens dynamically. Do *not* make any changes to this file. +1. Conf.d folder support has been added to ZoneMinder. Any custom + changes previously made to zm.conf must now be made in one or more custom + config files, created under the conf.d folder. Do this now. See + /etc/zm/conf.d/README for details. Once you recreate any custom config changes + under the conf.d folder, they will remain in place indefinitely. 2. Verify permissions of the zmuser account. @@ -139,12 +171,16 @@ Upgrades See step 2 of the Installation section to add missing permissions. -3. Verify the ZoneMinder Apache configuration file in the folder - /etc/httpd/conf.d. You will have a file called "zoneminder.conf" and there +3. Verify the ZoneMinder Nginx configuration file in the folder + /etc/zm/www. You will have a file called "zoneminder.conf" and there may also be a file called "zoneminder.conf.rpmnew". If the rpmnew file exists, inspect it and merge anything new in that file with zoneminder.conf. Verify the SSL REquirements meet your needs. Read README.https if necessary. + The contents of this file must be merged into your Nginx configuration. + See step 6 of the installation section if you have not already done this + during a previous upgrade. + 4. Upgrade the database before starting ZoneMinder. Most upgrades can be performed by executing the following command: diff --git a/distros/redhat/nginx/zoneminder.conf.in b/distros/redhat/nginx/zoneminder.conf.in index b8ffd816a..cca9af54f 100644 --- a/distros/redhat/nginx/zoneminder.conf.in +++ b/distros/redhat/nginx/zoneminder.conf.in @@ -22,6 +22,10 @@ location /cgi-bin-zm { fastcgi_pass unix:/run/fcgiwrap.sock; } +location /zm/cache { + alias "@ZM_CACHEDIR@"; +} + location /zm { gzip off; alias "@ZM_WEBDIR@"; diff --git a/distros/redhat/nginx/zoneminder.php-fpm.conf.in b/distros/redhat/nginx/zoneminder.php-fpm.conf.in index 26e8c62cf..ffc44bbe0 100644 --- a/distros/redhat/nginx/zoneminder.php-fpm.conf.in +++ b/distros/redhat/nginx/zoneminder.php-fpm.conf.in @@ -1,10 +1,12 @@ -# Change the user and group of the default pool to the web server account +; Change the user and group of the default pool to the web server account [www] user = @WEB_USER@ group = @WEB_GROUP@ -# Uncomment these on machines with little memory -#pm = ondemand -#pm.max_children = 10 -#pm.process_idle_timeout = 10s +; These parameters are typically a tradoff between performance and memory +; consumption. See the contents of www.conf for details. + +pm = ondemand +pm.max_children = 50 +pm.process_idle_timeout = 10s diff --git a/distros/redhat/nginx/zoneminder.tmpfiles.in b/distros/redhat/nginx/zoneminder.tmpfiles.in index 8040a7877..07bae0900 100644 --- a/distros/redhat/nginx/zoneminder.tmpfiles.in +++ b/distros/redhat/nginx/zoneminder.tmpfiles.in @@ -1,5 +1,8 @@ D @ZM_TMPDIR@ 0755 @WEB_USER@ @WEB_GROUP@ D @ZM_SOCKDIR@ 0755 @WEB_USER@ @WEB_GROUP@ +D @ZM_CACHEDIR@ 0755 @WEB_USER@ @WEB_GROUP@ +d @ZM_DIR_EVENTS@ 0755 @WEB_USER@ @WEB_GROUP@ +D @ZM_DIR_IMAGES@ 0755 @WEB_USER@ @WEB_GROUP@ D /var/lib/php/session 770 root @WEB_GROUP@ D /var/lib/php/wsdlcache 770 root @WEB_GROUP@ diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index de1d408ea..05d0cf653 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -84,7 +84,8 @@ BuildRequires: libmp4v2-devel BuildRequires: x264-devel %{?with_nginx:Requires: nginx} -%{?with_nginx:Requires: fcgiwrap} +# Enable only after fcgiwrap is in Fedora repos +#%{?with_nginx:Requires: fcgiwrap} %{?with_nginx:Requires: php-fpm} %{!?with_nginx:Requires: httpd} %{!?with_nginx:Requires: php} From 720505cbea163bf544b45cd861d6ae4592a174bb Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Sun, 4 Nov 2018 17:14:46 -0600 Subject: [PATCH 099/230] spelling --- distros/redhat/nginx/README.Fedora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distros/redhat/nginx/README.Fedora b/distros/redhat/nginx/README.Fedora index 013a502b0..90d760952 100644 --- a/distros/redhat/nginx/README.Fedora +++ b/distros/redhat/nginx/README.Fedora @@ -15,7 +15,7 @@ What's New 4. If you have installed ZoneMinder from the FedBerry repositories, this build of ZoneMinder has support for Raspberry Pi hardware acceleration when using ffmpeg. Unforunately, there is a problem with the same hardware acceleration - when using libvlc. Consequently, libvlc support in thie build of ZoneMinder + when using libvlc. Consequently, libvlc support in this build of ZoneMinder has been disabled until the problem is resolved. See the following bug report for details: https://trac.videolan.org/vlc/ticket/18594 From b8066e66ed6ab1b824fd6d058505c292d4873911 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Mon, 5 Nov 2018 06:51:08 -0600 Subject: [PATCH 100/230] Update zoneminder.spec --- distros/redhat/zoneminder.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 05d0cf653..4f51c73e4 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -307,7 +307,7 @@ EOF %{_libexecdir}/zoneminder/ %{_datadir}/zoneminder/ -%{_datadir}/applications/*%{name}.desktop +%{_datadir}/applications/*zoneminder.desktop %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/events From d3064e440296152e66974f89a773ef0751e8f0a0 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Mon, 5 Nov 2018 07:06:34 -0600 Subject: [PATCH 101/230] Update zoneminder.spec --- distros/redhat/zoneminder.spec | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 4f51c73e4..ecba0fb5a 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -84,8 +84,6 @@ BuildRequires: libmp4v2-devel BuildRequires: x264-devel %{?with_nginx:Requires: nginx} -# Enable only after fcgiwrap is in Fedora repos -#%{?with_nginx:Requires: fcgiwrap} %{?with_nginx:Requires: php-fpm} %{!?with_nginx:Requires: httpd} %{!?with_nginx:Requires: php} @@ -132,7 +130,7 @@ designed to support as many cameras as you can attach to your computer without too much degradation of performance. %prep -%autosetup -p 1 -a 1 -n ZoneMinder-%{version} +%autosetup -p 1 -a 1 %{__rm} -rf ./web/api/app/Plugin/Crud %{__mv} -f crud-%{crud_version} ./web/api/app/Plugin/Crud From ec951638105b320ac3a9940abe0b24f50f1d9b01 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Mon, 5 Nov 2018 19:35:05 -0600 Subject: [PATCH 102/230] Add fedora 29 support to buildsystem --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1ae0988d5..d46e0fc85 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,6 +38,7 @@ env: - OS=el DIST=7 - OS=fedora DIST=27 DOCKER_REPO=knnniggett/packpack - OS=fedora DIST=28 DOCKER_REPO=knnniggett/packpack + - OS=fedora DIST=29 DOCKER_REPO=knnniggett/packpack - OS=ubuntu DIST=trusty - OS=ubuntu DIST=xenial - OS=ubuntu DIST=trusty ARCH=i386 From c0c1247fa023e830345869fc86b9cff9abd817a0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Nov 2018 10:33:55 -0500 Subject: [PATCH 103/230] bump verion to 1.32.2 --- distros/redhat/zoneminder.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 32f052ecd..867942e4e 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -25,7 +25,7 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.32.1 +Version: 1.32.2 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons From 56bdd537575a3dd4531222ed6045707238266c70 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Nov 2018 10:40:44 -0500 Subject: [PATCH 104/230] Use the global dbh in ZoneMinder::Database instead of keeping our own copy of it in Logger --- scripts/ZoneMinder/lib/ZoneMinder/Logger.pm | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index 16535d9e3..2d15c045d 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -441,11 +441,11 @@ sub databaseLevel { $databaseLevel = $this->limit($databaseLevel); if ( $this->{databaseLevel} != $databaseLevel ) { if ( ( $databaseLevel > NOLOG ) and ( $this->{databaseLevel} <= NOLOG ) ) { - if ( !$this->{dbh} ) { - $this->{dbh} = ZoneMinder::Database::zmDbConnect(); + if ( ! ( $ZoneMinder::Database::dbh or ZoneMinder::Database::zmDbConnect() ) ) { + Warning("Failed connecting to db. Not using database logging."); + $this->{databaseLevel} = NOLOG; + return NOLOG; } - } elsif ( $databaseLevel <= NOLOG && $this->{databaseLevel} > NOLOG ) { - undef($this->{dbh}); } $this->{databaseLevel} = $databaseLevel; } @@ -558,12 +558,12 @@ sub logPrint { } if ( $level <= $this->{databaseLevel} ) { - if ( ! ( $this->{dbh} and $this->{dbh}->ping() ) ) { + if ( ! ( $ZoneMinder::Database::dbh and $ZoneMinder::Database::dbh->ping() ) ) { $this->{sth} = undef; # Turn this off because zDbConnect will do logging calls. my $oldlevel = $this->{databaseLevel}; $this->{databaseLevel} = NOLOG; - if ( ! ( $this->{dbh} = ZoneMinder::Database::zmDbConnect() ) ) { + if ( ! ZoneMinder::Database::zmDbConnect() ) { #print(STDERR "Can't log to database: "); return; } @@ -571,10 +571,10 @@ sub logPrint { } my $sql = 'INSERT INTO Logs ( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, NULL )'; - $this->{sth} = $this->{dbh}->prepare_cached($sql) if ! $this->{sth}; + $this->{sth} = $ZoneMinder::Database::dbh->prepare_cached($sql) if ! $this->{sth}; if ( !$this->{sth} ) { $this->{databaseLevel} = NOLOG; - Error("Can't prepare log entry '$sql': ".$this->{dbh}->errstr()); + Error("Can't prepare log entry '$sql': ".$ZoneMinder::Database::dbh->errstr()); return; } @@ -590,7 +590,7 @@ sub logPrint { ); if ( !$res ) { $this->{databaseLevel} = NOLOG; - Error("Can't execute log entry '$sql': ".$this->{dbh}->errstr()); + Error("Can't execute log entry '$sql': ".$ZoneMinder::Database::dbh->errstr()); } } # end if doing db logging } # end if level < effectivelevel From 0ebcef732491aece126378c8306a551e6929f8b6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Nov 2018 11:01:12 -0500 Subject: [PATCH 105/230] in poddoc and over needs a =back --- scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index d48747703..92085b07b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -309,6 +309,8 @@ saving configuration is a convenient way to ensure that the configuration held in the database corresponds with the most recent definitions and that all components are using the same set of configuration. +=back + =head2 EXPORT None by default. From 4107082845e396fb3dff0cc762ec41c7cfc040ce Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Nov 2018 11:01:49 -0500 Subject: [PATCH 106/230] Don't delete default states if there are none --- scripts/zmpkg.pl.in | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/scripts/zmpkg.pl.in b/scripts/zmpkg.pl.in index b29235f65..2d6593619 100644 --- a/scripts/zmpkg.pl.in +++ b/scripts/zmpkg.pl.in @@ -315,17 +315,19 @@ sub isActiveSanityCheck { if ( $sth->rows != 1 ) { # PP - no row, or too many rows. Either case is an error Info( 'Fixing States table - either no default state or duplicate default states' ); - $sql = "DELETE FROM States WHERE Name='default'"; - $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute() - or Fatal( "Can't execute: ".$sth->errstr() ); - $sql = q`"INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');`; + if ( $sth->rows ) { + $sql = q`DELETE FROM States WHERE Name='default'`; + $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute() + or Fatal( "Can't execute: ".$sth->errstr() ); + } + $sql = q`INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');`; $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); $res = $sth->execute() or Fatal("Can't execute: ".$sth->errstr()); - } + } # PP - Now make sure no two states have IsActive=1 $sql = 'SELECT Name FROM States WHERE IsActive = 1'; From 0e3eb0df17a64ddd09eef53bfae6c78ad508887f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Nov 2018 11:26:48 -0500 Subject: [PATCH 107/230] remove extra quotes, google code style, update pod docs --- scripts/ZoneMinder/lib/ZoneMinder/General.pm | 150 ++++++++----------- 1 file changed, 65 insertions(+), 85 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/General.pm b/scripts/ZoneMinder/lib/ZoneMinder/General.pm index 900a8985f..d16c248d5 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/General.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/General.pm @@ -1,27 +1,3 @@ -# ========================================================================== -# -# ZoneMinder General Utility Module, $Date$, $Revision$ -# Copyright (C) 2001-2008 Philip Coombes -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# -# ========================================================================== -# -# This module contains the common definitions and functions used by the rest -# of the ZoneMinder scripts -# package ZoneMinder::General; use 5.006; @@ -42,7 +18,7 @@ our @ISA = qw(Exporter ZoneMinder::Base); # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( - 'functions' => [ qw( + functions => [ qw( executeShellCommand getCmdFormat runCommand @@ -56,7 +32,7 @@ our %EXPORT_TAGS = ( ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); +our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); our @EXPORT = qw(); @@ -80,74 +56,74 @@ sub executeShellCommand { my $output = qx( $command ); my $status = $? >> 8; if ( $status || logDebugging() ) { - Debug( "Command: $command\n" ); + Debug("Command: $command"); chomp( $output ); - Debug( "Output: $output\n" ); + Debug("Output: $output"); } - return( $status ); + return $status; } sub getCmdFormat { - Debug( "Testing valid shell syntax\n" ); + Debug("Testing valid shell syntax"); my ( $name ) = getpwuid( $> ); if ( $name eq $Config{ZM_WEB_USER} ) { - Debug( "Running as '$name', su commands not needed\n" ); - return( "" ); + Debug("Running as '$name', su commands not needed"); + return ''; } - my $null_command = "true"; + my $null_command = 'true'; - my $prefix = "sudo -u ".$Config{ZM_WEB_USER}." "; - my $suffix = ""; + my $prefix = 'sudo -u '.$Config{ZM_WEB_USER}.' '; + my $suffix = ''; my $command = $prefix.$null_command.$suffix; - Debug( "Testing \"$command\"\n" ); + Debug("Testing \"$command\""); my $output = qx($command 2>&1); my $status = $? >> 8; $output //= $!; if ( !$status ) { - Debug( "Test ok, using format \"$prefix$suffix\"\n" ); + Debug("Test ok, using format \"$prefix$suffix\""); return( $prefix, $suffix ); } else { chomp( $output ); - Debug( "Test failed, '$output'\n" ); + Debug("Test failed, '$output'"); - $prefix = "su ".$Config{ZM_WEB_USER}." --shell=/bin/sh --command='"; - $suffix = "'"; + $prefix = 'su '.$Config{ZM_WEB_USER}.q` --shell=/bin/sh --command='`; + $suffix = q`'`; $command = $prefix.$null_command.$suffix; - Debug( "Testing \"$command\"\n" ); + Debug("Testing \"$command\""); my $output = qx($command 2>&1); my $status = $? >> 8; $output //= $!; if ( !$status ) { - Debug( "Test ok, using format \"$prefix$suffix\"\n" ); + Debug("Test ok, using format \"$prefix$suffix\""); return( $prefix, $suffix ); } else { - chomp( $output ); - Debug( "Test failed, '$output'\n" ); + chomp($output); + Debug("Test failed, '$output'"); $prefix = "su ".$Config{ZM_WEB_USER}." -c '"; $suffix = "'"; $command = $prefix.$null_command.$suffix; - Debug( "Testing \"$command\"\n" ); + Debug("Testing \"$command\""); $output = qx($command 2>&1); $status = $? >> 8; $output //= $!; if ( !$status ) { - Debug( "Test ok, using format \"$prefix$suffix\"\n" ); + Debug("Test ok, using format \"$prefix$suffix\""); return( $prefix, $suffix ); } else { - chomp( $output ); - Debug( "Test failed, '$output'\n" ); + chomp($output); + Debug("Test failed, '$output'"); } } } - Error( "Unable to find valid 'su' syntax\n" ); - exit( -1 ); -} + Error("Unable to find valid 'su' syntax"); + exit -1; +} # end sub getCmdFormat our $testedShellSyntax = 0; our ( $cmdPrefix, $cmdSuffix ); @@ -161,23 +137,23 @@ sub runCommand { } my $command = shift; - $command = $Config{ZM_PATH_BIN}."/".$command; + $command = $Config{ZM_PATH_BIN}.'/'.$command; if ( $cmdPrefix ) { $command = $cmdPrefix.$command.$cmdSuffix; } - Debug( "Command: $command\n" ); + Debug("Command: $command"); my $output = qx($command); my $status = $? >> 8; - chomp( $output ); + chomp($output); if ( $status || logDebugging() ) { if ( $status ) { - Error( "Unable to run \"$command\", output is \"$output\", status is $status\n" ); + Error("Unable to run \"$command\", output is \"$output\", status is $status"); } else { - Debug( "Output: $output\n" ); + Debug("Output: $output"); } } - return( $output ); -} + return $output; +} # end sub runCommand sub createEventPath { my $event = shift; @@ -210,7 +186,7 @@ sub _checkProcessOwner { $_setFileOwner = 0; } } - return( $_setFileOwner ); + return $_setFileOwner; } sub setFileOwner { @@ -219,7 +195,7 @@ sub setFileOwner { if ( _checkProcessOwner() ) { chown( $_ownerUid, $_ownerGid, $file ) or Fatal( "Can't change ownership of file '$file' to '" - .$Config{ZM_WEB_USER}.":".$Config{ZM_WEB_GROUP}."': $!" + .$Config{ZM_WEB_USER}.':'.$Config{ZM_WEB_GROUP}."': $!" ); } } @@ -234,13 +210,13 @@ sub _checkForImageInfo { }; $_hasImageInfo = $@?0:1; } - return( $_hasImageInfo ); + return $_hasImageInfo; } sub createEvent { my $event = shift; - Debug( "Creating event" ); + Debug('Creating event'); #print( Dumper( $event )."\n" ); _checkForImageInfo(); @@ -561,38 +537,33 @@ __END__ =head1 NAME -ZoneMinder::Database - Perl extension for blah blah blah +ZoneMinder::General - Utility Functions for ZoneMinder =head1 SYNOPSIS -use ZoneMinder::Database; +use ZoneMinder::General; blah blah blah =head1 DESCRIPTION -Stub documentation for ZoneMinder, created by h2xs. It looks like the -author of the extension was negligent enough to leave the stub -unedited. - -Blah blah blah. +This module contains the common definitions and functions used by the rest +of the ZoneMinder scripts =head2 EXPORT -None by default. + functions => [ qw( + executeShellCommand + getCmdFormat + runCommand + setFileOwner + createEventPath + createEvent + makePath + jsonEncode + jsonDecode + ) ] - -=head1 SEE ALSO - -Mention other useful documentation such as the documentation of -related modules or operating system documentation (such as man pages - in UNIX), or any relevant external documentation such as RFCs or -standards. - -If you have a mailing list set up for your module, mention it here. - -If you have a web site set up for your module, mention it here. - =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE @@ -601,9 +572,18 @@ Philip Coombes, Ephilip.coombes@zoneminder.comE Copyright (C) 2001-2008 Philip Coombes -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself, either Perl version 5.8.3 or, -at your option, any later version of Perl 5 you may have available. +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. =cut From 230ce61dc417506ecf38ee6ef509e2900ae4b1db Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Nov 2018 11:54:18 -0500 Subject: [PATCH 108/230] fix #2296 by prepending bind with :: --- src/zm_stream.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index 7ab844d78..461a6d74d 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -325,7 +325,7 @@ void StreamBase::openComms() { strncpy(loc_addr.sun_path, loc_sock_path, sizeof(loc_addr.sun_path)); loc_addr.sun_family = AF_UNIX; Debug(3, "Binding to %s", loc_sock_path); - if ( bind(sd, (struct sockaddr *)&loc_addr, strlen(loc_addr.sun_path)+sizeof(loc_addr.sun_family)+1) < 0 ) { + if ( ::bind(sd, (struct sockaddr *)&loc_addr, strlen(loc_addr.sun_path)+sizeof(loc_addr.sun_family)+1) < 0 ) { Fatal("Can't bind: %s", strerror(errno)); } From a066968acaf3dd61f484c71b32c4d90736d8e67c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Nov 2018 12:33:18 -0500 Subject: [PATCH 109/230] fix dbError and cause it to return the error string instead of just logging it. Add error logging of db errors that don't throw exceptions. --- web/includes/database.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/web/includes/database.php b/web/includes/database.php index 55535659f..95fabee11 100644 --- a/web/includes/database.php +++ b/web/includes/database.php @@ -98,7 +98,14 @@ function dbLog( $sql, $update=false ) { } function dbError( $sql ) { - Error( "SQL-ERR '".$dbConn->errorInfo()."', statement was '".$sql."'" ); + global $dbConn; + $error = $dbConn->errorInfo(); + if ( ! $error[0] ) + return ''; + + $message = "SQL-ERR '".implode("\n",$dbConn->errorInfo())."', statement was '".$sql."'"; + Error($message); + return $message; } function dbEscape( $string ) { @@ -136,6 +143,10 @@ function dbQuery( $sql, $params=NULL ) { Logger::Debug("SQL: $sql values:" . ($params?implode(',',$params):'') ); } $result = $dbConn->query($sql); + if ( ! $result ) { + Error("SQL: Error preparing $sql: " . $pdo->errorInfo); + return NULL; + } } if ( defined('ZM_DB_DEBUG') ) { if ( $params ) From 702143e51bd8f0f52d9700491b8ca7e96665f66c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 7 Nov 2018 12:33:54 -0500 Subject: [PATCH 110/230] Create a function called getBodyTopHTML that outputs the body tag and anything else that should go at the top. Things like the we require javascript message, and any other messages like error messages. Use this on the monitor and console view to stick an error message at the top when saving a monitor fails. This is a pretty quick, crude implementation. --- web/includes/actions.php | 4 +++- web/index.php | 1 + web/skins/classic/includes/functions.php | 22 +++++++++++++++++----- web/skins/classic/views/console.php | 2 +- web/skins/classic/views/monitor.php | 2 +- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/web/includes/actions.php b/web/includes/actions.php index de0861fe0..48082a585 100644 --- a/web/includes/actions.php +++ b/web/includes/actions.php @@ -554,7 +554,8 @@ if ( canEdit('Monitors') ) { $maxSeq = dbFetchOne('SELECT MAX(Sequence) AS MaxSequence FROM Monitors', 'MaxSequence'); $changes[] = 'Sequence = '.($maxSeq+1); - if ( dbQuery('INSERT INTO Monitors SET '.implode(', ', $changes)) ) { + $sql = 'INSERT INTO Monitors SET '.implode(', ', $changes); + if ( dbQuery($sql) ) { $mid = dbInsertId(); $zoneArea = $_REQUEST['newMonitor']['Width'] * $_REQUEST['newMonitor']['Height']; dbQuery("INSERT INTO Zones SET MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) ); @@ -566,6 +567,7 @@ if ( canEdit('Monitors') ) { } else { Error('Error saving new Monitor.'); + $error_message = dbError($sql); return; } } else { diff --git a/web/index.php b/web/index.php index addddce06..cbda1096a 100644 --- a/web/index.php +++ b/web/index.php @@ -170,6 +170,7 @@ if ( !is_writable(ZM_DIR_EVENTS) || !is_writable(ZM_DIR_IMAGES) ) { } # Globals +$error_message = null; $redirect = null; $view = null; if ( isset($_REQUEST['view']) ) diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index 7847e37c7..8fd9e2a8e 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -208,6 +208,23 @@ echo output_link_if_exists( array( + +'; + global $error_message; + if ( $error_message ) { + echo '
    '.$error_message.'
    '; + } +} // end function getBodyTopHTML + function getNavBarHTML($reload = null) { # Provide a facility to turn off the headers if you put headers=0 into the url if ( isset($_REQUEST['navbar']) and $_REQUEST['navbar']=='0' ) @@ -235,11 +252,6 @@ function getNavBarHTML($reload = null) { $running = daemonCheck(); $status = $running?translate('Running'):translate('Stopped'); ?> -