Merge branch 'master' into server_path_prefix

This commit is contained in:
Isaac Connor 2018-08-22 12:57:16 -04:00
commit 6e5472d305
101 changed files with 3670 additions and 18615 deletions

View File

@ -1,6 +1,6 @@
You should only file an issue if you found a bug. Feature and enhancement requests, general discussions and support questions should occur in one of the following areas:
- The ZoneMinder IRC channel - irc.freenode.net #zoneminder
- The [ZoneMinder-Chat Slack channel](https://zoneminder-chat.herokuapp.com/)
- The [ZoneMinder Forum](https://forums.zoneminder.com/)
**Do not post feature or enhancement requests, general discussions or support questions here.**

View File

@ -802,6 +802,24 @@ if(WITH_SYSTEMD)
endif(NOT POLKIT_FOUND)
endif(WITH_SYSTEMD)
# Find the path to an arp compatible executable
if(ZM_PATH_ARP STREQUAL "")
find_program(ARP_EXECUTABLE arp)
if(ARP_EXECUTABLE)
set(ZM_PATH_ARP "${ARP_EXECUTABLE}")
mark_as_advanced(ARP_EXECUTABLE)
else(ARP_EXECUTABLE)
find_program(ARP_EXECUTABLE ip)
if(ARP_EXECUTABLE)
set(ZM_PATH_ARP "${ARP_EXECUTABLE} neigh")
mark_as_advanced(ARP_EXECUTABLE)
endif(ARP_EXECUTABLE)
endif(ARP_EXECUTABLE)
if(ARP_EXECUTABLE-NOTFOUND)
message(WARNING "Unable to find a compatible arp binary. Monitor probe will not function." )
endif(ARP_EXECUTABLE-NOTFOUND)
endif(ZM_PATH_ARP STREQUAL "")
# Some variables that zm expects
set(ZM_PID "${ZM_RUNDIR}/zm.pid")
set(ZM_CONFIG "${ZM_CONFIG_DIR}/zm.conf")

View File

@ -3,6 +3,8 @@ ZoneMinder
[![Build Status](https://travis-ci.org/ZoneMinder/zoneminder.png)](https://travis-ci.org/ZoneMinder/zoneminder) [![Bountysource](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received)
[![Join Slack](https://github.com/ozonesecurity/ozonebase/blob/master/img/slacksm.png?raw=true)](https://zoneminder-chat.herokuapp.com)
All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org
## Overview

View File

@ -50,4 +50,4 @@ ZM_PATH_SWAP=@ZM_TMPDIR@
# Full path to optional arp binary
# ZoneMinder will find the arp binary automatically on most systems
ZM_PATH_ARP=@ZM_PATH_ARP@
ZM_PATH_ARP="@ZM_PATH_ARP@"

View File

@ -517,6 +517,7 @@ CREATE TABLE `Monitors` (
`ArchivedEvents` int(10) default NULL,
`ArchivedEventDiskSpace` bigint default NULL,
`ZoneCount` TINYINT NOT NULL DEFAULT 0,
`Refresh` int(10) unsigned default NULL,
PRIMARY KEY (`Id`)
) ENGINE=@ZM_MYSQL_ENGINE@;
@ -581,7 +582,7 @@ DROP TABLE IF EXISTS `Stats`;
CREATE TABLE `Stats` (
`MonitorId` int(10) unsigned NOT NULL default '0',
`ZoneId` int(10) unsigned NOT NULL default '0',
`EventId` int(10) unsigned NOT NULL default '0',
`EventId` BIGINT UNSIGNED NOT NULL,
`FrameId` int(10) unsigned NOT NULL default '0',
`PixelDiff` tinyint(3) unsigned NOT NULL default '0',
`AlarmPixels` int(10) unsigned NOT NULL default '0',
@ -778,7 +779,9 @@ INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-423','Ffmpeg','Reolink',0,0,1,1
INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-411','Ffmpeg','Reolink',0,0,1,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-420','Ffmpeg','Reolink',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'IOS Camera','Ffmpeg','IPCAMIOS',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'Dericam P2','Ffmpeg','DericamP2',0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,45,0,0,1,0,0,0,0,1,1,45,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'Trendnet','Remote','Trendnet',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0);
--
-- Add some monitor preset values
--
@ -820,6 +823,7 @@ INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg','Remote','http',0,0,'
INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/GetData.cgi',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'IP Webcam by Pavel Khlebovich 1920x1080','Remote','/dev/video<?>','0',255,'http','simple','<ip-address>','8080','/video','',1920,1080,0,NULL,0,'0','','',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'VEO Observer, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Blue Net Video Server, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/cgi-bin/image.cgi?control=0&id=admin&passwd=admin',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,'ACTi IP, mpeg4, unicast','Remote',NULL,NULL,NULL,'rtsp','rtpUni','<ip-address>',7070,'','/track',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);

View File

@ -16,7 +16,7 @@ SET @s = (SELECT IF(
AND column_name = 'Refresh'
) > 0,
"SELECT 'Column Refresh exists in Monitors'",
"ALTER TABLE Monitors ADD `Refresh` int(10) unsigned default NULL"
"ALTER TABLE Monitors ADD `Refresh` int(10) unsigned default NULL AFTER `ZoneCount`"
));
PREPARE stmt FROM @s;

23
db/zm_update-1.31.45.sql Normal file
View File

@ -0,0 +1,23 @@
--
-- This updates a 1.31.44 database to 1.31.45
--
-- Add WebSite enum to Monitor.Type
-- Add Refresh column to Monitors table
-- This is the same as the update to 1.31.43, but due to Refresh not being added to zm_create.sql.in we need to have it
-- again in order to fix people who did a fresh install from 1.31.43 or 1.31.44.
--
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Monitors'
AND table_schema = DATABASE()
AND column_name = 'Refresh'
) > 0,
"SELECT 'Column Refresh exists in Monitors'",
"ALTER TABLE Monitors ADD `Refresh` int(10) unsigned default NULL AFTER `ZoneCount`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

2
db/zm_update-1.31.46.sql Normal file
View File

@ -0,0 +1,2 @@
ALTER TABLE Stats MODIFY COLUMN EventId bigint unsigned NOT NULL;

15
db/zm_update-1.31.47.sql Normal file
View File

@ -0,0 +1,15 @@
--
-- Add Prefix column to Storage
--
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Servers'
AND column_name = 'PathPrefix'
) > 0,
"SELECT 'Column PathPrefix already exists in Servers'",
"ALTER TABLE Servers ADD `PathPrefix` TEXT AFTER `Hostname`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

View File

@ -13,6 +13,7 @@ Build-Depends: debhelper (>= 9), cmake
, libv4l-dev (>= 0.8.3)
, libbz2-dev
, ffmpeg | libav-tools
, net-tools
, libnetpbm10-dev
, libvlccore-dev, libvlc-dev
, libcurl4-gnutls-dev | libcurl4-nss-dev | libcurl4-openssl-dev

View File

@ -8,16 +8,6 @@ SET(zmgid_final www)
SET(webroot /srv/www/htdocs)
SET(zm_webdir ${webroot}/zoneminder)
# Download jscalendar & move files into position
file(DOWNLOAD http://downloads.sourceforge.net/jscalendar/jscalendar-1.0.zip ${CMAKE_CURRENT_SOURCE_DIR}/jscalendar-1.0.zip STATUS download_jsc)
if(download_jsc EQUAL 0)
message(STATUS "Jscalander successfully downloaded. Installing...")
execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/jscalendar.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ERROR_VARIABLE unzip_jsc)
message(STATUS "Status of jscalender script was: ${unzip_jsc}")
else(download_jsc EQUAL 0)
message(STATUS "Unable to download optional jscalander. Skipping...")
endif(download_jsc EQUAL 0)
# Create several empty folders
file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp)
@ -45,7 +35,3 @@ install(FILES zoneminder.tmpfiles DESTINATION /etc/tmpfiles.d RENAME zoneminder.
install(FILES redalert.wav DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/sounds PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(FILES zoneminder.service DESTINATION /usr/lib/systemd/system PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
# Install jscalendar
if(unzip_jsc STREQUAL "")
install(DIRECTORY jscalendar-1.0/ DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/tools/jscalendar)
endif(unzip_jsc STREQUAL "")

View File

@ -1,7 +0,0 @@
#!/bin/bash
unzip -o jscalendar-1.0.zip
mkdir -v jscalendar-doc
cd jscalendar-1.0
mv -v *html *php doc/* README ../jscalendar-doc
rmdir -v doc

View File

@ -16,7 +16,6 @@ Version: 1.27.0
Release: 1%{?dist}
Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons
# jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/
# Mootools is under the MIT license: http://mootools.net/
License: GPLv2+ and LGPLv2+ and MIT
URL: http://www.zoneminder.com/
@ -141,7 +140,7 @@ fi
%files
%defattr(-,root,root,-)
%doc AUTHORS COPYING README.md distros/opensuse/README.OpenSuse distros/opensuse/jscalendar-doc
%doc AUTHORS COPYING README.md distros/opensuse/README.OpenSuse
%docdir /opt/zoneminder/share/man
%config %attr(640,root,%{zmgid_final}) /etc/zm.conf
%config(noreplace) %attr(644,root,root) /etc/apache2/conf.d/zoneminder.conf

View File

@ -27,18 +27,6 @@ else(ZM_WEB_USER STREQUAL "nginx")
configure_file(systemd/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY)
endif(ZM_WEB_USER STREQUAL "nginx")
# Unpack jscalendar & move files into position
message(STATUS "Unpacking and Installing jscalendar...")
execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/misc/jscalendar.sh
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
ERROR_VARIABLE unzip_jsc
)
if("${unzip_jsc}" STREQUAL "")
message(STATUS "jscalendar successfully installed.")
else("${unzip_jsc}" STREQUAL "")
message(FATAL_ERROR "\nAn error occurred while jscalendar was being processed:\n${unzip_jsc}")
endif("${unzip_jsc}" STREQUAL "")
# Create several empty folders
file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp)
@ -58,7 +46,6 @@ install(CODE "execute_process(COMMAND ln -sf ../../java/cambozola.jar \"\$ENV{DE
# Install auxiliary files
install(FILES misc/redalert.wav DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/sounds PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(DIRECTORY jscalendar-1.0/ DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/tools/jscalendar)
# Install zoneminder service files
install(FILES zoneminder.logrotate DESTINATION /etc/logrotate.d RENAME zoneminder PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)

View File

@ -1,7 +0,0 @@
#!/bin/bash
unzip -o misc/jscalendar-1.0.zip
mkdir -v jscalendar-doc
cd jscalendar-1.0
mv -v *html *php doc/* README ../jscalendar-doc
rmdir -v doc

View File

@ -26,11 +26,10 @@
%global _hardened_build 1
Name: zoneminder
Version: 1.31.44
Version: 1.31.45
Release: 1%{?dist}
Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons
# jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/
# Mootools is inder the MIT license: http://mootools.net/
# CakePHP is under the MIT license: https://github.com/cakephp/cakephp
# Crud is under the MIT license: https://github.com/FriendsOfCake/crud
@ -53,6 +52,7 @@ BuildRequires: pcre-devel
BuildRequires: libjpeg-turbo-devel
BuildRequires: findutils
BuildRequires: coreutils
BuildRequires: net-tools
BuildRequires: perl
BuildRequires: perl-generators
BuildRequires: perl(Archive::Tar)
@ -252,7 +252,7 @@ EOF
%files
%license COPYING
%doc AUTHORS README.md distros/redhat/readme/README.%{readme_suffix} distros/redhat/readme/README.https distros/redhat/jscalendar-doc
%doc AUTHORS README.md distros/redhat/readme/README.%{readme_suffix} distros/redhat/readme/README.https
# We want these two folders to have "normal" read permission
# compared to the folder contents

View File

@ -11,6 +11,8 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa
,libavformat-dev (>= 6:10~)
,libavutil-dev (>= 6:10~)
,libswscale-dev (>= 6:10~)
,ffmpeg | libav-tools
,net-tools
,libbz2-dev
,libgcrypt-dev | libgcrypt11-dev
,libcurl4-gnutls-dev

View File

@ -13,8 +13,8 @@ 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.
Security
^^^^^^^^^
Login, Logout & API Security
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The APIs tie into ZoneMinder's existing security model. This means if you have
OPT_AUTH enabled, you need to log into ZoneMinder using the same browser you plan to
use the APIs from. If you are developing an app that relies on the API, you need
@ -23,12 +23,33 @@ to do a POST login from the app into ZoneMinder before you can access the API.
Then, you need to re-use the authentication information of the login (returned as cookie states)
with subsequent APIs for the authentication information to flow through to the APIs.
This means if you plan to use cuRL to experiment with these APIs, you first need to do
This means if you plan to use cuRL to experiment with these APIs, you first need to login:
**Login process for ZoneMinder v1.32.0 and above**
::
curl -XPOST -d "user=XXXX&pass=YYYY" -c cookies.txt http://yourzmip/zm/api/login.json
Staring ZM 1.32.0, you also have a `logout` API that basically clears your session. It looks like this:
::
curl -b cookies.txt http://yourzmip/zm/api/logout.json
**Login process for older versions of ZoneMinder**
::
curl -d "username=XXXX&password=YYYY&action=login&view=console" -c cookies.txt http://yourzmip/zm/index.php
The equivalent logout process for older versions of ZoneMinder is:
::
curl -XPOST -d "username=XXXX&password=YYYY&action=logout&view=console" -b cookies.txt http://yourzmip/zm/index.php
replacing *XXXX* and *YYYY* with your username and password, respectively.
Please make sure you do this in a directory where you have write permissions, otherwise cookies.txt will not be created
@ -45,16 +66,46 @@ using CuRL like so:
This would return a list of monitors and pass on the authentication information to the ZM API layer.
So remember, if you are using authentication, please add a ``-b cookies.txt`` to each of the commands below if you are using
CuRL. If you are not using CuRL and writing your own app, you need to make sure you pass on cookies to subsequent requests
in your app.
A deeper dive into the login process
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
As you might have seen above, there are two ways to login, one that uses the `login.json` API and the other that logs in using the ZM portal. If you are running ZoneMinder 1.32.0 and above, it is *strongly* recommended you use the `login.json` approach. The "old" approach will still work but is not as powerful as the API based login. Here are the reasons why:
* The "old" approach basically uses the same login webpage (`index.php`) that a user would log into when viewing the ZM console. This is not really using an API and more importantly, if you have additional components like reCAPTCHA enabled, this will not work. Using the API approach is much cleaner and will work irrespective of reCAPTCHA
* The new login API returns important information that you can use to stream videos as well, right after login. Consider for example, a typical response to the login API (`/login.json`):
::
{
"credentials": "auth=f5b9cf48693fe8552503c8ABCD5",
"append_password": 0,
"version": "1.31.44",
"apiversion": "1.0"
}
In this example I have `OPT_AUTH` enabled in ZoneMinder and it returns my credential key. You can then use this key to stream images like so:
::
<img src="https://server/zm/cgi-bin/nph-zms?monitor=1&auth=<authval>" />
Where `authval` is the credentials returned to start streaming videos.
The `append_password` field will contain 1 when it is necessary for you to append your ZM password. This is the case when you set `AUTH_RELAY` in ZM options to "plain", for example. In that case, the `credentials` field may contain something like `&user=admin&pass=` and you have to add your password to that string.
.. NOTE:: It is recommended you invoke the `login` API once every 60 minutes to make sure the session stays alive. The same is true if you use the old login method too.
Examples (please read security notice above)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You will see each URL ending in either ``.xml`` or ``.json``. This is the
format of the request, and it determines the format that any data returned to
you will be in. I like json, however you can use xml if you'd like.
Please remember, if you are using authentication, please add a ``-b cookies.txt`` to each of the commands below if you are using
CuRL. If you are not using CuRL and writing your own app, you need to make sure you pass on cookies to subsequent requests
in your app.
(In all examples, replace 'server' with IP or hostname & port where ZoneMinder is running)

View File

@ -149,7 +149,7 @@ BEGIN {
foreach my $str ( <$CONFIG> ) {
next if ( $str =~ /^\s*$/ );
next if ( $str =~ /^\s*#/ );
my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*(.*?)\s*$/;
my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/;
if ( ! $name ) {
print( STDERR "Warning, bad line in $config_file: $str\n" );
next;

View File

@ -0,0 +1,475 @@
# ==========================================================================
#
# ZoneMinder Dericam P2 Control Protocol Module
# Copyright (C) Roman Dissertori
#
# 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 implementation of the Dericam P2 device control protocol
#
package ZoneMinder::Control::DericamP2;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
our %CamParams = ();
# ==========================================================================
#
# Dericam P2 Control Protocol
#
# On ControlAddress use the format :
# USERNAME:PASSWORD@ADDRESS:PORT
# eg : admin:@10.1.2.1:80
# zoneminder:zonepass@10.0.100.1:40000
#
# ==========================================================================
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
use Time::HiRes qw( usleep );
sub open
{
my $self = shift;
$self->loadMonitor();
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION );
$self->{state} = 'open';
}
sub printMsg
{
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendCmd
{
my $self = shift;
my $cmd = shift;
my $result = undef;
printMsg( $cmd, "Tx" );
my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" );
Info( "http://".$self->{Monitor}->{ControlAddress}."/$cmd".$self->{Monitor}->{ControlDevice} );
my $res = $self->{ua}->request($req);
if ( $res->is_success )
{
$result = !undef;
}
else
{
Error( "Error check failed:'".$res->status_line()."'" );
}
return( $result );
}
sub getCamParams
{
my $self = shift;
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=getimageattr";
my $req = $self->sendCmd( $cmd );
my $res = $self->{ua}->request($req);
if ( $res->is_success )
{
# Parse results setting values in %FCParams
my $content = $res->decoded_content;
while ($content =~ s/var\s+([^=]+)=([^;]+);//ms) {
$CamParams{$1} = $2;
}
}
else
{
Error( "Error check failed:'".$res->status_line()."'" );
}
}
#autoStop
#This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab
sub autoStop
{
my $self = shift;
my $stop_command = shift;
my $autostop = shift;
if( $stop_command && $autostop)
{
Debug( "Auto Stop" );
usleep( $autostop );
my $cmd = "decoder_control.cgi?command=".$stop_command;
$self->sendCmd( $cmd );
}
}
# Reset the Camera
sub reset
{
my $self = shift;
Debug( "Camera Reset" );
# Move to default position
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=home";
$self->sendCmd( $cmd );
# Reset all other values to default
$cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-image_type=1&-default=on";
$self->sendCmd( $cmd );
}
# Reboot Camera (on Sleep button)
sub sleep
{
my $self = shift;
Debug( "Camera Reboot" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=sysreboot";
$self->sendCmd( $cmd );
}
# Stop the Camera
sub moveStop
{
my $self = shift;
Debug( "Camera Stop" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=stop";
$self->sendCmd( $cmd );
}
#Up Arrow
sub moveConUp
{
my $self = shift;
my $stop_command = "1";
Debug( "Move Up" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=up&-speed=45";
$self->sendCmd( $cmd );
#$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} );
}
#Down Arrow
sub moveConDown
{
my $self = shift;
my $stop_command = "3";
Debug( "Move Down" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=down&-speed=45";
$self->sendCmd( $cmd );
#$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} );
}
#Left Arrow
sub moveConLeft
{
my $self = shift;
my $stop_command = "5";
Debug( "Move Left" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=left&-speed=45";
$self->sendCmd( $cmd );
#$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} );
}
#Right Arrow
sub moveConRight
{
my $self = shift;
my $stop_command = "7";
Debug( "Move Right" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=right&-speed=45";
$self->sendCmd( $cmd );
#$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} );
}
#Zoom In
sub zoomConTele
{
my $self = shift;
Debug( "Zoom Tele" );
my $cmd = "decoder_control.cgi?command=18";
$self->sendCmd( $cmd );
}
#Zoom Out
sub zoomConWide
{
my $self = shift;
Debug( "Zoom Wide" );
my $cmd = "decoder_control.cgi?command=16";
$self->sendCmd( $cmd );
}
#Diagonally Up Right Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConUpRight
{
my $self = shift;
Debug( "Move Diagonally Up Right" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=upright&-speed=45";
$self->sendCmd( $cmd );
}
#Diagonally Down Right Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConDownRight
{
my $self = shift;
Debug( "Move Diagonally Down Right" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=downright&-speed=45";
$self->sendCmd( $cmd );
}
#Diagonally Up Left Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConUpLeft
{
my $self = shift;
Debug( "Move Diagonally Up Left" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=upleft&-speed=45";
$self->sendCmd( $cmd );
}
#Diagonally Down Left Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConDownLeft
{
my $self = shift;
Debug( "Move Diagonally Down Left" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=downnleft&-speed=45";
$self->sendCmd( $cmd );
}
#Set Camera Preset
#Presets must be translated into values internal to the camera
#Those values are: 30,32,34,36,38,40,42,44 for presets 1-8 respectively
sub presetSet
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
Debug( "Set Preset $preset" );
if (( $preset >= 1 ) && ( $preset <= 8 )) {
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=preset&-act=set&-number=".(($preset*2) + 28);
$self->sendCmd( $cmd );
}
}
#Recall Camera Preset
#Presets must be translated into values internal to the camera
#Those values are: 31,33,35,37,39,41,43,45 for presets 1-8 respectively
sub presetGoto
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
Debug( "Goto Preset $preset" );
if (( $preset >= 1 ) && ( $preset <= 8 )) {
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=preset&-act=goto&-number=".(($preset*2) + 29);
$self->sendCmd( $cmd );
}
if ( $preset == 9 ) {
$self->horizontalPatrol();
}
if ( $preset == 10 ) {
$self->verticalPatrol();
}
}
#Horizontal Patrol
sub horizontalPatrol
{
my $self = shift;
Debug( "Horizontal Patrol" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=hscan";
$self->sendCmd( $cmd );
}
#Vertical Patrol
sub verticalPatrol
{
my $self = shift;
Debug( "Vertical Patrol" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=vscan";
$self->sendCmd( $cmd );
}
# Increase Brightness
sub irisAbsOpen
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'brightness'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'brightness'} += $step;
$CamParams{'brightness'} = 100 if ($CamParams{'brightness'} > 100);
Debug( "Increase Brightness" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-brightness=".$CamParams{'brightness'};
$self->sendCmd( $cmd );
}
# Decrease Brightness
sub irisAbsClose
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'brightness'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'brightness'} -= $step;
$CamParams{'brightness'} = 0 if ($CamParams{'brightness'} < 0);
Debug( "Decrease Brightness" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-brightness=".$CamParams{'brightness'};
$self->sendCmd( $cmd );
}
# Increase Contrast
sub whiteAbsIn
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'contrast'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'contrast'} += $step;
$CamParams{'contrast'} = 100 if ($CamParams{'contrast'} > 100);
Debug( "Increase Contrast" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-contrast=".$CamParams{'contrast'};
$self->sendCmd( $cmd );
}
# Decrease Contrast
sub whiteAbsOut
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'contrast'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'contrast'} -= $step;
$CamParams{'contrast'} = 0 if ($CamParams{'contrast'} < 0);
Debug( "Decrease Contrast" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-contrast=".$CamParams{'contrast'};
$self->sendCmd( $cmd );
}
#TODO Saturation cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=44 [0-255]
sub satIncrease
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'saturation'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'saturation'} += $step;
$CamParams{'saturation'} = 255 if ($CamParams{'saturation'} > 255);
Debug( "Increase Saturation" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=".$CamParams{'saturation'};
$self->sendCmd( $cmd );
}
sub satDecrease
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'saturation'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'saturation'} -= $step;
$CamParams{'saturation'} = 0 if ($CamParams{'saturation'} < 0);
Debug( "Decrease Saturation" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=".$CamParams{'saturation'};
$self->sendCmd( $cmd );
}
#TODO Sharpness cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=37 [0-100]
sub sharpIncrease
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'sharpness'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'sharpness'} += $step;
$CamParams{'sharpness'} = 100 if ($CamParams{'sharpness'} > 100);
Debug( "Increase Sharpness" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=".$CamParams{'sharpness'};
$self->sendCmd( $cmd );
}
sub sharpDecrease
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'sharpness'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'sharpness'} -= $step;
$CamParams{'sharpness'} = 0 if ($CamParams{'sharpness'} < 0);
Debug( "Decrease Sharpness" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=".$CamParams{'sharpness'};
$self->sendCmd( $cmd );
}
#TODO Hue cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=37 [0-100]
sub hueIncrease
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'hue'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'hue'} += $step;
$CamParams{'hue'} = 100 if ($CamParams{'hue'} > 100);
Debug( "Increase Hue" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=".$CamParams{'hue'};
$self->sendCmd( $cmd );
}
sub hueDecrease
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'hue'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'hue'} -= $step;
$CamParams{'hue'} = 0 if ($CamParams{'hue'} < 0);
Debug( "Decrease Hue" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=".$CamParams{'hue'};
$self->sendCmd( $cmd );
}
1;

View File

@ -0,0 +1,326 @@
# ==========================================================================
#
# ZoneMinder iPhone Control Protocol Module, $Date: 2018-07-15 00:20:00 +0000 $, $Revision: 0003 $
# Copyright (C) 2001-2008 Philip Coombes
#
# Modified for iPhone ipcamera for IOS BY PETER ZARGLIS n 2018-06-09 13:45:00
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ==========================================================================
#
# This module contains the implementation of the iPhone ipcamera for IOS
# control protocol.
#
# ==========================================================================
package ZoneMinder::Control::IPCAMIOS;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
our $VERSION = $ZoneMinder::Base::VERSION;
# ==========================================================================
#
# iPhone ipcamera for IOS Protocol
#
# ==========================================================================
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
use Time::HiRes qw( usleep );
my $loopfactor=100000;
sub new
{
my $class = shift;
my $id = shift;
my $self = ZoneMinder::Control->new( $id );
my $logindetails = "";
bless( $self, $class );
srand( time() );
return $self;
}
our $AUTOLOAD;
sub AUTOLOAD
{
my $self = shift;
my $class = ref($self) || croak( "$self not object" );
my $name = $AUTOLOAD;
$name =~ s/.*://;
if ( exists($self->{$name}) )
{
return( $self->{$name} );
}
Fatal( "Can't access $name member of object of class $class" );
}
sub open
{
my $self = shift;
$self->loadMonitor();
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent" );
$self->{state} = 'open';
}
sub close
{
my $self = shift;
$self->{state} = 'closed';
}
sub sendCmd
{
my $self = shift;
my $cmd = shift;
my $result = undef;
my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" );
my $res = $self->{ua}->request($req);
if ( $res->is_success )
{
$result = $res->decoded_content;
}
else
{
Error( "Error check failed: '".$res->status_line()."'" );
}
return( $result );
}
sub getDisplayAttr
{
my $self = shift;
my $param = shift;
my $cmdget = "parameters?";
my $resp = $self->sendCmd( $cmdget );
my @fields = split(',',$resp);
my $response=$fields[$param];
my @buffer=split(':',$response);
my $response2=$buffer[1];
return ($response2);
}
sub sleep
{
}
# Flip image vertically -> Horz -> off
sub moveConUp
{
my $self = shift;
my $params = shift;
Debug( "Flip Image" );
my $dvalue=$self->getDisplayAttr(3);
if ( $dvalue == 2 )
{
$dvalue=0;
my $cmd = "parameters?flip=$dvalue";
$self->sendCmd( $cmd );
}
else
{
$dvalue=$dvalue+1;
my $cmd = "parameters?flip=$dvalue";
$self->sendCmd( $cmd );
}
}
# Change camera (front facing or back)
sub moveConDown
{
my $self = shift;
my $params = shift;
Debug( "Change Camera" );
my $dvalue=$self->getDisplayAttr(7);
if ( $dvalue == 0 )
{
my $cmd = "parameters?camera=1";
$self->sendCmd( $cmd );
}
else
{
my $cmd = "parameters?camera=0";
$self->sendCmd( $cmd );
}
}
# Picture Orientation Clockwise
sub moveConRight
{
my $self = shift;
my $params = shift;
Debug( "Orientation" );
my $dvalue=$self->getDisplayAttr(10);
if ( $dvalue == 1 )
{
$dvalue=4;
my $cmd = "parameters?rotation=$dvalue";
$self->sendCmd( $cmd );
}
else
{
$dvalue=$dvalue-1;
my $cmd = "parameters?rotation=$dvalue";
$self->sendCmd( $cmd );
}
}
# Picture Orientation Anti-Clockwise
sub moveConLeft
{
my $self = shift;
my $params = shift;
Debug( "Orientation" );
my $dvalue=$self->getDisplayAttr(10);
if ( $dvalue == 4 )
{
$dvalue=1;
my $cmd = "parameters?rotation=$dvalue";
$self->sendCmd( $cmd );
}
else
{
$dvalue=$dvalue+1;
my $cmd = "parameters?rotation=$dvalue";
$self->sendCmd( $cmd );
}
}
# presetHome is used to turn off Torch, unlock Focus, unlock Exposure, unlock white-balance, rotation, image flipping
# Just basically reset all the little variables and set it to medium quality
# Rotation = 0 means it will autoselect using built in detection
sub presetHome
{
my $self = shift;
Debug( "Home Preset" );
my $cmd = "parameters?torch=0&focus=0&wb=0&exposure=0&rotation=0&flip=0&quality=0.5";
$self->sendCmd( $cmd );
}
sub focusAbsNear
# Focus Un/Lock
{
my $self = shift;
my $params = shift;
Debug( "Focus Un/Lock" );
my $dvalue=$self->getDisplayAttr(2);
if ( $dvalue == 0 )
{
my $cmd = "parameters?focus=1";
$self->sendCmd( $cmd );
}
else
{
my $cmd = "parameters?focus=0";
$self->sendCmd( $cmd );
}
}
sub focusAbsFar
# Exposure Un/Lock
{
my $self = shift;
my $params = shift;
Debug( "Exposure Un/Lock" );
my $dvalue=$self->getDisplayAttr(11);
if ( $dvalue == 0 )
{
my $cmd = "parameters?exposure=1";
$self->sendCmd( $cmd );
}
else
{
my $cmd = "parameters?exposure=0";
$self->sendCmd( $cmd );
}
}
# Increase stream Quality (from 0 to 10)
sub irisAbsOpen
{
my $self = shift;
my $params = shift;
Debug( "Quality" );
my $dvalue=$self->getDisplayAttr(8);
if ( $dvalue < 1 )
{
$dvalue=$dvalue+0.1;
my $cmd = "parameters?quality=$dvalue";
$self->sendCmd( $cmd );
}
}
# Decrease stream Quality (from 10 to 0)
sub irisAbsClose
{
my $self = shift;
my $params = shift;
Debug( "Quality" );
my $dvalue=$self->getDisplayAttr(8);
if ( $dvalue > 0 )
{
$dvalue=$dvalue-0.1;
my $cmd = "parameters?quality=$dvalue";
$self->sendCmd( $cmd );
}
}
# White Balance Un/Lock
sub whiteAbsIn
{
my $self = shift;
my $params = shift;
Debug( "White Balance" );
my $dvalue=$self->getDisplayAttr(9);
if ( $dvalue == 0 )
{
my $cmd = "parameters?wb=1";
$self->sendCmd( $cmd );
}
else
{
my $cmd = "parameters?wb=0";
$self->sendCmd( $cmd );
}
}
# Torch control on/off
sub whiteAbsOut
{
my $self = shift;
my $params = shift;
Debug( "Torch" );
my $dvalue=$self->getDisplayAttr(5);
if ( $dvalue == 0 )
{
my $cmd = "parameters?torch=1";
$self->sendCmd( $cmd );
}
else
{
my $cmd = "parameters?torch=0";
$self->sendCmd( $cmd );
}
}
1;

View File

@ -74,39 +74,36 @@ use ZoneMinder::Config qw(:all);
use Time::HiRes qw( usleep );
sub open
{
sub open {
my $self = shift;
$self->loadMonitor();
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION );
$self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION);
$self->{state} = 'open';
}
sub printMsg
{
sub printMsg {
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
Debug($msg.'['.$msg_len.']');
}
sub sendCmd
{
sub sendCmd {
my $self = shift;
my $cmd = shift;
my $msg = shift;
my $content_type = shift;
my $result = undef;
printMsg( $cmd, "Tx" );
printMsg($cmd, 'Tx');
my $server_endpoint = "http://".$self->{Monitor}->{ControlAddress}."/$cmd";
my $server_endpoint = 'http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd;
my $req = HTTP::Request->new(POST => $server_endpoint);
$req->header('content-type' => $content_type);
$req->header('Host' => $self->{Monitor}->{ControlAddress});
@ -122,14 +119,13 @@ sub sendCmd
} else {
Error("After sending PTZ command, camera returned the following error:'".$res->status_line()."'");
}
return( $result );
return $result;
}
sub getCamParams
{
sub getCamParams {
my $self = shift;
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken></GetImagingSettings></s:Body></s:Envelope>';
my $server_endpoint = "http://".$self->{Monitor}->{ControlAddress}."/onvif/imaging";
my $server_endpoint = 'http://'.$self->{Monitor}->{ControlAddress}.'/onvif/imaging';
my $req = HTTP::Request->new(POST => $server_endpoint);
$req->header('content-type' => 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/GetImagingSettings"');
$req->header('Host' => $self->{Monitor}->{ControlAddress});
@ -150,22 +146,19 @@ sub getCamParams
if ( $content =~ /.*<tt:(Contrast)>(.+)<\/tt:Contrast>.*/ ) {
$CamParams{$1} = $2;
}
}
else
{
} else {
Error("Unable to retrieve camera image settings:'".$res->status_line()."'");
}
}
#autoStop
#This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab
sub autoStop
{
sub autoStop {
my $self = shift;
my $autostop = shift;
if ( $autostop ) {
Debug( "Auto Stop" );
Debug('Auto Stop');
my $cmd = 'onvif/PTZ';
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PanTilt>true</PanTilt><Zoom>false</Zoom></Stop></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
@ -175,20 +168,18 @@ sub autoStop
}
# Reset the Camera
sub reset
{
Debug( "Camera Reset" );
sub reset {
Debug('Camera Reset');
my $self = shift;
my $cmd = "";
my $cmd = '';
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SystemReboot xmlns="http://www.onvif.org/ver10/device/wsdl"/></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/device/wsdl/SystemReboot"';
$self->sendCmd($cmd, $msg, $content_type);
}
#Up Arrow
sub moveConUp
{
Debug( "Move Up" );
sub moveConUp {
Debug('Move Up');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -198,9 +189,8 @@ sub moveConUp
}
#Down Arrow
sub moveConDown
{
Debug( "Move Down" );
sub moveConDown {
Debug('Move Down');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -210,9 +200,8 @@ sub moveConDown
}
#Left Arrow
sub moveConLeft
{
Debug( "Move Left" );
sub moveConLeft {
Debug('Move Left');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.49" y="0" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -222,9 +211,8 @@ sub moveConLeft
}
#Right Arrow
sub moveConRight
{
Debug( "Move Right" );
sub moveConRight {
Debug('Move Right');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.49" y="0" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -234,9 +222,8 @@ sub moveConRight
}
#Zoom In
sub zoomConTele
{
Debug( "Zoom Tele" );
sub zoomConTele {
Debug('Zoom Tele');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><Zoom x="0.49" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -246,9 +233,8 @@ sub zoomConTele
}
#Zoom Out
sub zoomConWide
{
Debug( "Zoom Wide" );
sub zoomConWide {
Debug('Zoom Wide');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><Zoom x="-0.49" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -259,9 +245,8 @@ sub zoomConWide
#Diagonally Up Right Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConUpRight
{
Debug( "Move Diagonally Up Right" );
sub moveConUpRight {
Debug('Move Diagonally Up Right');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.5" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -272,9 +257,8 @@ sub moveConUpRight
#Diagonally Down Right Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConDownRight
{
Debug( "Move Diagonally Down Right" );
sub moveConDownRight {
Debug('Move Diagonally Down Right');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.5" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -285,9 +269,8 @@ sub moveConDownRight
#Diagonally Up Left Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConUpLeft
{
Debug( "Move Diagonally Up Left" );
sub moveConUpLeft {
Debug('Move Diagonally Up Left');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.5" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -298,9 +281,8 @@ sub moveConUpLeft
#Diagonally Down Left Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConDownLeft
{
Debug( "Move Diagonally Down Left" );
sub moveConDownLeft {
Debug('Move Diagonally Down Left');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.5" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -310,9 +292,8 @@ sub moveConDownLeft
}
#Stop
sub moveStop
{
Debug( "Move Stop" );
sub moveStop {
Debug('Move Stop');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PanTilt>true</PanTilt><Zoom>false</Zoom></Stop></s:Body></s:Envelope>';
@ -321,8 +302,7 @@ sub moveStop
}
#Set Camera Preset
sub presetSet
{
sub presetSet {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
@ -334,8 +314,7 @@ sub presetSet
}
#Recall Camera Preset
sub presetGoto
{
sub presetGoto {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
@ -348,45 +327,42 @@ sub presetGoto
#Horizontal Patrol
#To be determined if this camera supports this feature
sub horizontalPatrol
{
Debug( "Horizontal Patrol" );
sub horizontalPatrol {
Debug('Horizontal Patrol');
my $self = shift;
my $cmd = '';
my $msg ='';
my $content_type = '';
# $self->sendCmd( $cmd, $msg, $content_type );
Error( "PTZ Command not implemented in control script." );
Error('PTZ Command not implemented in control script.');
}
#Horizontal Patrol Stop
#To be determined if this camera supports this feature
sub horizontalPatrolStop
{
Debug( "Horizontal Patrol Stop" );
sub horizontalPatrolStop {
Debug('Horizontal Patrol Stop');
my $self = shift;
my $cmd = '';
my $msg ='';
my $content_type = '';
# $self->sendCmd( $cmd, $msg, $content_type );
Error( "PTZ Command not implemented in control script." );
Error('PTZ Command not implemented in control script.');
}
# Increase Brightness
sub irisAbsOpen
{
Debug( "Iris $CamParams{'Brightness'}" );
sub irisAbsOpen {
Debug("Iris $CamParams{Brightness}");
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'Brightness'});
$self->getCamParams() unless($CamParams{Brightness});
my $step = $self->getParam($params, 'step');
my $max = 100;
$CamParams{'Brightness'} += $step;
$CamParams{'Brightness'} = $max if ($CamParams{'Brightness'} > $max);
$CamParams{Brightness} += $step;
$CamParams{Brightness} = $max if ($CamParams{Brightness} > $max);
my $cmd = 'onvif/imaging';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Brightness'}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{Brightness}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
$self->sendCmd( $cmd, $msg, $content_type );
}
@ -394,57 +370,55 @@ sub irisAbsOpen
# Decrease Brightness
sub irisAbsClose
{
Debug( "Iris $CamParams{'Brightness'}" );
Debug( "Iris $CamParams{Brightness}" );
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'brightness'});
$self->getCamParams() unless($CamParams{brightness});
my $step = $self->getParam( $params, 'step' );
my $min = 0;
$CamParams{'Brightness'} -= $step;
$CamParams{'Brightness'} = $min if ($CamParams{'Brightness'} < $min);
$CamParams{Brightness} -= $step;
$CamParams{Brightness} = $min if ($CamParams{Brightness} < $min);
my $cmd = 'onvif/imaging';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Brightness'}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{Brightness}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
$self->sendCmd( $cmd, $msg, $content_type );
}
# Increase Contrast
sub whiteAbsIn
{
Debug( "Iris $CamParams{'Contrast'}" );
sub whiteAbsIn {
Debug("Iris $CamParams{Contrast}");
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'Contrast'});
$self->getCamParams() unless($CamParams{Contrast});
my $step = $self->getParam( $params, 'step' );
my $max = 100;
$CamParams{'Contrast'} += $step;
$CamParams{'Contrast'} = $max if ($CamParams{'Contrast'} > $max);
$CamParams{Contrast} += $step;
$CamParams{Contrast} = $max if ($CamParams{Contrast} > $max);
my $cmd = 'onvif/imaging';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Contrast'}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{Contrast}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
}
# Decrease Contrast
sub whiteAbsOut
{
Debug( "Iris $CamParams{'Contrast'}" );
sub whiteAbsOut {
Debug("Iris $CamParams{Contrast}");
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'Contrast'});
$self->getCamParams() unless($CamParams{Contrast});
my $step = $self->getParam($params, 'step');
my $min = 0;
$CamParams{'Contrast'} -= $step;
$CamParams{'Contrast'} = $min if ($CamParams{'Contrast'} < $min);
$CamParams{Contrast} -= $step;
$CamParams{Contrast} = $min if ($CamParams{Contrast} < $min);
my $cmd = 'onvif/imaging';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Contrast'}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{Contrast}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
}
1;
__END__

View File

@ -1,504 +0,0 @@
# =========================================================================
#
# ZoneMinder Trendnet TV-IP862IC IP Control Protocol Module, $Date: $, $Revision: $
# Copyright (C) 2014 Vincent Giovannone
#
#
# ==========================================================================
#
# 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 implementation of the Trendnet TV-IP672PI IP camera control
# protocol. Also works or TV-IP862IC
#
# For Zoneminder 1.26+
#
# Under control capability:
#
# * Main: name it (suggest TVIP672PI), type is FFMPEG (or remote if you're using MJPEG), protocol is TVIP672PI
# * Main (more): Can wake, can sleep, can reset
# * Move: Can move, can move diagonally, can move mapped, can move relative
# * Pan: Can pan
# * Tilt: Can tilt
# * Presets: Has presets, num presets 20, has home preset (don't set presets via camera's web server, only set via ZM.)
#
# Under control tab in the monitor itself:
#
# * Controllable
# * Control type is the name you gave it in control capability above
# * Control device is the password you use to authenticate to the camera (see further below if you need to change the username from "admin")
# * Control address is the camera's ip address AND web port. example: 192.168.1.1:80
#
#
# If using with anything but a TV-IP672PI (ex: TV-IP672WI), YOU MUST MATCH THE REALM TO MATCH YOUR CAMERA FURTHER DOWN!
#
#
# Due to how the TVIP672 represents presets internally, you MUST define the presets in order... i.e. 1,2,3,4... not 1,10,3,4.
# (see much further down for why, if you care...)
#
package ZoneMinder::Control::TVIP862;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
#
# ******** YOU MUST CHANGE THE FOLLOWING LINES TO MATCH YOUR CAMERA! **********
#
# I assume that "TV-IP672WI" would work for the TV-IP672WI, but can't test since I don't own one.
#
# TV-IP672PI works for the PI version, of course.
#
# Finally, the username is the username you'd like to authenticate as.
#
our $REALM = 'TV-IP862IC';
our $USERNAME = 'admin';
our $PASSWORD = '';
our $ADDRESS = '';
# ==========================================================================
#
# Trendnet TV-IP672PI Control Protocol
#
# ==========================================================================
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
sub open
{
my $self = shift;
$self->loadMonitor();
my ( $protocol, $username, $password, $address )
= $self->{Monitor}->{ControlAddress} =~ /^(https?:\/\/)?([^:]+):([^\/@]+)@(.*)$/;
if ( $username ) {
$USERNAME = $username;
$PASSWORD = $password;
$ADDRESS = $address;
} else {
Error( "Failed to parse auth from address");
$ADDRESS = $self->{Monitor}->{ControlAddress};
}
if ( ! $ADDRESS =~ /:/ ) {
Error( "You generally need to also specify the port. I will append :80" );
$ADDRESS .= ':80';
}
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION );
$self->{state} = 'open';
# credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string)
Debug ( "sendCmd credentials control address:'".$ADDRESS
."' realm:'" . $REALM
. "' username:'" . $USERNAME
. "' password:'".$PASSWORD
."'"
);
$self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD);
# Detect REALM
my $req = HTTP::Request->new( GET=>"http://".$ADDRESS."/cgi/ptdc.cgi" );
my $res = $self->{ua}->request($req);
if ( ! $res->is_success ) {
Debug("Need newer REALM");
if ( $res->status_line() eq '401 Unauthorized' ) {
my $headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
} # end foreach
if ( $$headers{'www-authenticate'} ) {
my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
if ( $tokens =~ /\w+="([^"]+)"/i ) {
$REALM = $1;
Debug( "Changing REALM to $REALM" );
$self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD);
} # end if
} else {
Debug("No headers line");
} # end if headers
} # end if $res->status_line() eq '401 Unauthorized'
} # end if ! $res->is_success
}
sub printMsg
{
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendCmd
{
# This routine is used for all moving, which are all GET commands...
my $self = shift;
my $cmd = shift;
my $result = undef;
my $url = "http://".$ADDRESS."/cgi/ptdc.cgi?command=".$cmd;
my $req = HTTP::Request->new( GET=>$url );
Debug ("sendCmd command: " . $url );
my $res = $self->{ua}->request($req);
if ( $res->is_success ) {
$result = !undef;
} else {
if ( $res->status_line() eq '401 Unauthorized' ) {
Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
Error("Content was " . $res->content() );
my $res = $self->{ua}->request($req);
if ( $res->is_success ) {
$result = !undef;
} else {
Error("Content was " . $res->content() );
}
}
if ( ! $result ) {
Error( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" );
}
}
return( $result );
}
sub sendCmdPost
{
#
# This routine is used for setting/clearing presets and IR commands, which are POST commands...
#
my $self = shift;
my $url = shift;
my $cmd = shift;
my $result = undef;
if ($url eq undef)
{
Error ("url passed to sendCmdPost is undefined.");
return(-1);
}
Debug ("sendCmdPost url: " . $url . " cmd: " . $cmd);
my $req = HTTP::Request->new(POST => "http://".$ADDRESS.$url);
$req->content_type('application/x-www-form-urlencoded');
$req->content($cmd);
Debug ( "sendCmdPost credentials control address:'".$ADDRESS."' realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD."'");
my $res = $self->{ua}->request($req);
if ( $res->is_success )
{
$result = !undef;
}
else
{
Error( "sendCmdPost Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" );
if ( $res->status_line() eq '401 Unauthorized' ) {
Error( "sendCmdPost Error check failed: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
} else {
Error( "sendCmdPost Error check failed: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
} # endif
}
return( $result );
}
sub move
{
my $self = shift;
my $panSteps = shift;
my $tiltSteps = shift;
my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps";
$self->sendCmd( $cmd );
}
sub moveRelUpLeft
{
my $self = shift;
Debug( "Move Up Left" );
$self->move(-3, 3);
}
sub moveRelUp
{
my $self = shift;
Debug( "Move Up" );
$self->move(0, 3);
}
sub moveRelUpRight
{
my $self = shift;
Debug( "Move Up Right" );
$self->move(3, 3);
}
sub moveRelLeft
{
my $self = shift;
Debug( "Move Left" );
$self->move(-3, 0);
}
sub moveRelRight
{
my $self = shift;
Debug( "Move Right" );
$self->move(3, 0);
}
sub moveRelDownLeft
{
my $self = shift;
Debug( "Move Down Left" );
$self->move(-3, -3);
}
sub moveRelDown
{
my $self = shift;
Debug( "Move Down" );
$self->move(0, -3);
}
sub moveRelDownRight
{
my $self = shift;
Debug( "Move Down Right" );
$self->move(3, -3);
}
# moves the camera to center on the point that the user clicked on in the video image.
# This isn't mega accurate but good enough for most purposes
sub moveMap
{
# If the camera moves too much, increase hscale and vscale. (...if it doesn't move enough, try decreasing!)
# They scale the movement and are here to compensate for manufacturing variation.
# It's never going to be perfect, so just get somewhere in the ballpark and call it a day.
# (Don't forget to kill the zmcontrol process while tweaking!)
# 1280x800
my $hscale = 31;
my $vscale = 25;
# 1280x800 with fisheye
#my $hscale = 15;
#my $vscale = 15;
# 640x400
#my $hscale = 14;
#my $vscale = 12;
my $self = shift;
my $params = shift;
my $xcoord = $self->getParam( $params, 'xcoord' );
my $ycoord = $self->getParam( $params, 'ycoord' );
my $hor = ($xcoord - ($self->{Monitor}->{Width} / 2))/$hscale;
my $ver = ($ycoord - ($self->{Monitor}->{Height} / 2))/$vscale;
$hor = int($hor);
$ver = -1 * int($ver);
Debug( "Move Map to $xcoord,$ycoord, hor=$hor, ver=$ver" );
$self->move( $hor, $ver );
}
# **** PRESETS ****
#
# OK, presets work a little funky but they DO work, provided you define them in order and don't skip any.
#
# The problem is that when you load the web page for this camera, it gives a list of preset names tied to index numbers.
# So let's say you have four presets... A, B, C, and D, and defined them in that order.
# So A is index 0, B is index 1, C is index 2, D is index 3. When you tell the camera to go to a preset, you actually tell it by number, not by name.
# (So "Go to D" is really "go to index 3".)
#
# Now let's say somebody deletes C via the camera's web GUI. The camera re-numbers the existing presets A=0, B=1, D=2.
# There's really no easy way for ZM to discover this re-numbering, so zoneminder would still send "go to preset 3" thinking
# it's telling the camera to go to point D. In actuality it's telling the camera to go to a preset that no longer exists.
#
# As long as you define your presets in order (i.e. define preset 1, then preset 2, then preset 3, etc.) everything will work just
# fine in ZoneMinder.
#
# (Home preset needs to be set via the camera's web gui, and is unaffected by any of this.)
#
# So that's the limitation: DEFINE YOUR PRESETS IN ORDER THROUGH (and only through!) ZM AND DON'T SKIP ANY.
#
sub presetClear
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
my $cmd = "presetName=$preset&command=del";
my $url = "/eng/admin/cam_control.cgi";
Debug ("presetClear: " . $preset . " cmd: " . $cmd);
$self->sendCmdPost($url,$cmd);
}
sub presetSet
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
my $cmd = "presetName=$preset&command=add";
my $url = "/eng/admin/cam_control.cgi";
Debug ("presetSet " . $preset . " cmd: " . $cmd);
$self->sendCmdPost ($url,$cmd);
}
sub presetGoto
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
$preset = $preset - 1;
Debug( "Goto Preset $preset" );
my $cmd = "goto_preset_position&index=$preset";
$self->sendCmd( $cmd );
}
sub presetHome
{
my $self = shift;
Debug( "Home Preset" );
my $cmd = "go_home";
$self->sendCmd( $cmd );
}
#
# **** IR CONTROLS ****
#
#
# Wake: Force IR on, always. (always night mode)
#
# Sleep: Force IR off, always. (always day mode)
#
# Reset: Automatic IR mode. (day/night mode determined by camera)
#
sub wake
{
# force IR on ("always night mode")
my $self = shift;
my $url = "/eng/admin/adv_audiovideo.cgi";
my $cmd = "irMode=3";
Debug("Wake -- IR on");
$self->sendCmdPost ($url,$cmd);
}
sub sleep
{
# force IR off ("always day mode")
my $self=shift;
my $url = "/eng/admin/adv_audiovideo.cgi";
my $cmd = "irMode=2";
Debug("Sleep -- IR off");
$self->sendCmdPost ($url,$cmd);
}
sub reset
{
# IR auto
my $self=shift;
my $url = "/eng/admin/adv_audiovideo.cgi";
my $cmd = "irMode=0";
Debug("Reset -- IR auto");
$self->sendCmdPost ($url,$cmd);
}
1;
__END__
# Below is stub documentation for your module. You'd better edit it!
=head1 NAME
ZoneMinder::Database - Perl extension for Trendnet TVIP672
=head1 SYNOPSIS
use ZoneMinder::Database;
stuff this in /usr/share/perl5/ZoneMinder/Control , then eat a sandwich
=head1 DESCRIPTION
Stub documentation for Trendnet TVIP672, created by Vince.
=head2 EXPORT
None by default.
=head1 SEE ALSO
Read the comments at the beginning of this file to see the usage for zoneminder 1.25.0
=head1 AUTHOR
Vincent Giovannone, I'd rather you not email me.
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2014 by Vincent Giovannone
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.
=cut

View File

@ -0,0 +1,431 @@
package ZoneMinder::Control::Trendnet;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
# You do not need to change the REALM, but you can get slightly faster response
# by setting so that the first auth request succeeds.
#
# The username and password should be passed in the ControlAddress field but you
# can set them here if you want.
#
our $REALM = '';
our $PROTOCOL = 'http://';
our $USERNAME = 'admin';
our $PASSWORD = '';
our $ADDRESS = '';
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
sub open {
my $self = shift;
$self->loadMonitor();
if ( ( $self->{Monitor}->{ControlAddress} =~ /^(?<PROTOCOL>https?:\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>.*)$/ ) ) {
$PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL};
$USERNAME = $+{USERNAME} if $+{USERNAME};
$PASSWORD = $+{PASSWORD} if $+{PASSWORD};
$ADDRESS = $+{ADDRESS} if $+{ADDRESS};
} else {
Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress});
$ADDRESS = $self->{Monitor}->{ControlAddress};
}
if ( !($ADDRESS =~ /:/) ) {
Error('You generally need to also specify the port. I will append :80');
$ADDRESS .= ':80';
}
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent('ZoneMinder Control Agent/'.$ZoneMinder::Base::ZM_VERSION);
$self->{state} = 'closed';
# credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string)
Debug ( "sendCmd credentials control address:'".$ADDRESS
."' realm:'" . $REALM
. "' username:'" . $USERNAME
. "' password:'".$PASSWORD
."'"
);
$self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD);
# Detect REALM
my $res = $self->{ua}->get($PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi');
if ( $res->is_success ) {
$self->{state} = 'open';
return;
}
if ( $res->status_line() eq '401 Unauthorized' ) {
my $headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
}
if ( $$headers{'www-authenticate'} ) {
my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
if ( $tokens =~ /\w+="([^"]+)"/i ) {
if ( $REALM ne $1 ) {
$REALM = $1;
Debug("Changing REALM to $REALM");
$self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD);
$res = $self->{ua}->get($PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi');
if ( $res->is_success() ) {
$self->{state} = 'open';
return;
}
Error('Authentication still failed after updating REALM' . $res->status_line);
$headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
} # end foreach
} else {
Error('Authentication failed, not a REALM problem');
}
} else {
Error('Failed to match realm in tokens');
} # end if
} else {
Debug('No headers line');
} # end if headers
} # end if $res->status_line() eq '401 Unauthorized'
} # end sub open
sub printMsg {
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug($msg.'['.$msg_len.']');
}
sub sendCmd {
# This routine is used for all moving, which are all GET commands...
my $self = shift;
my $cmd = shift;
my $url = $PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi?command='.$cmd;
my $res = $self->{ua}->get($url);
Debug('sendCmd command: ' . $url);
if ( $res->is_success ) {
return !undef;
}
Error("Error check failed: '".$res->status_line()."' cmd:'".$cmd."'");
return;
}
sub sendCmdPost {
#
# This routine is used for setting/clearing presets and IR commands, which are POST commands...
#
my $self = shift;
my $url = shift;
my $form = shift;
my $result = undef;
if ( $url eq undef ) {
Error('url passed to sendCmdPost is undefined.');
return -1;
}
#Debug('sendCmdPost url: ' . $url . ' cmd: ' . $cmd);
my $res;
$res = $self->{ua}->post(
$PROTOCOL.$ADDRESS.$url,
Referer=>$PROTOCOL.$ADDRESS.$url,
Content=>$form
);
Debug("sendCmdPost credentials control to: $PROTOCOL$ADDRESS$url realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD."'");
if ( $res->is_success ) {
return !undef;
}
Error("sendCmdPost Error check failed: '".$res->status_line()."' cmd:");
return $result;
} # end sub sendCmdPost
sub move {
my $self = shift;
my $panSteps = shift;
my $tiltSteps = shift;
my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps";
$self->sendCmd($cmd);
}
sub moveRelUpLeft {
my $self = shift;
Debug('Move Up Left');
$self->move(-3, 3);
}
sub moveRelUp {
my $self = shift;
Debug('Move Up');
$self->move(0, 3);
}
sub moveRelUpRight {
my $self = shift;
Debug('Move Up Right');
$self->move(3, 3);
}
sub moveRelLeft {
my $self = shift;
Debug('Move Left');
$self->move(-3, 0);
}
sub moveRelRight {
my $self = shift;
Debug('Move Right');
$self->move(3, 0);
}
sub moveRelDownLeft {
my $self = shift;
Debug('Move Down Left');
$self->move(-3, -3);
}
sub moveRelDown {
my $self = shift;
Debug('Move Down');
$self->move(0, -3);
}
sub moveRelDownRight {
my $self = shift;
Debug('Move Down Right');
$self->move(3, -3);
}
# moves the camera to center on the point that the user clicked on in the video image.
# This isn't mega accurate but good enough for most purposes
sub moveMap {
# If the camera moves too much, increase hscale and vscale. (...if it doesn't move enough, try decreasing!)
# They scale the movement and are here to compensate for manufacturing variation.
# It's never going to be perfect, so just get somewhere in the ballpark and call it a day.
# (Don't forget to kill the zmcontrol process while tweaking!)
# 1280x800
my $hscale = 31;
my $vscale = 25;
# 1280x800 with fisheye
#my $hscale = 15;
#my $vscale = 15;
# 640x400
#my $hscale = 14;
#my $vscale = 12;
my $self = shift;
my $params = shift;
my $xcoord = $self->getParam( $params, 'xcoord' );
my $ycoord = $self->getParam( $params, 'ycoord' );
my $hor = ($xcoord - ($self->{Monitor}->{Width} / 2))/$hscale;
my $ver = ($ycoord - ($self->{Monitor}->{Height} / 2))/$vscale;
$hor = int($hor);
$ver = -1 * int($ver);
Debug("Move Map to $xcoord,$ycoord, hor=$hor, ver=$ver");
$self->move($hor, $ver);
}
# **** PRESETS ****
#
# OK, presets work a little funky but they DO work, provided you define them
# in order and don't skip any.
#
# The problem is that when you load the web page for this camera, it gives a
# list of preset names tied to index numbers.
# So let's say you have four presets... A, B, C, and D, and defined them in
# that order.
# So A is index 0, B is index 1, C is index 2, D is index 3. When you tell
# the camera to go to a preset, you actually tell it by number, not by name.
# (So "Go to D" is really "go to index 3".)
#
# Now let's say somebody deletes C via the camera's web GUI. The camera
# re-numbers the existing presets A=0, B=1, D=2.
# There's really no easy way for ZM to discover this re-numbering, so
# zoneminder would still send "go to preset 3" thinking
# it's telling the camera to go to point D. In actuality it's telling the
# camera to go to a preset that no longer exists.
#
# As long as you define your presets in order (i.e. define preset 1, then
# preset 2, then preset 3, etc.) everything will work just
# fine in ZoneMinder.
#
# (Home preset needs to be set via the camera's web gui, and is unaffected by
# any of this.)
#
# So that's the limitation: DEFINE YOUR PRESETS IN ORDER THROUGH (and only
# through!) ZM AND DON'T SKIP ANY.
#
sub presetClear {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
my $cmd = "presetName=$preset&command=del";
my $url = '/eng/admin/cam_control.cgi';
Debug('presetClear: ' . $preset . ' cmd: ' . $cmd);
$self->sendCmdPost($url,{presetName=>$preset, command=>'del'});
}
sub presetSet {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
my $cmd = "presetName=$preset&command=add";
my $url = '/eng/admin/cam_control.cgi';
Debug('presetSet ' . $preset . ' cmd: ' . $cmd);
$self->sendCmdPost($url,{presetName=>$preset, command=>'add', Submit=>'Add'});
}
sub presetGoto {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
$preset = $preset - 1;
Debug("Goto Preset $preset");
my $cmd = "goto_preset_position&index=$preset";
$self->sendCmd($cmd);
}
sub presetHome {
my $self = shift;
Debug('Home Preset');
my $cmd = 'go_home';
$self->sendCmd($cmd);
}
#
# **** IR CONTROLS ****
#
#
# Wake: Force IR on, always. (always night mode)
#
# Sleep: Force IR off, always. (always day mode)
#
# Reset: Automatic IR mode. (day/night mode determined by camera)
#
sub wake {
# force IR on ("always night mode")
my $self = shift;
my $url = '/eng/admin/adv_audiovideo.cgi';
my $cmd = 'irMode=3';
Debug('Wake -- IR on');
$self->sendCmdPost($url,$cmd);
}
sub sleep {
# force IR off ("always day mode")
my $self = shift;
my $url = '/eng/admin/adv_audiovideo.cgi';
my $cmd = 'irMode=2';
Debug('Sleep -- IR off');
$self->sendCmdPost($url,$cmd);
}
sub reset {
# IR auto
my $self=shift;
my $url = '/eng/admin/adv_audiovideo.cgi';
my $cmd = 'irMode=0';
Debug('Reset -- IR auto');
$self->sendCmdPost($url,$cmd);
}
1;
__END__
=head1 NAME
ZoneMinder::Control::Trendnet - Perl module for Trendnet cameras
=head1 SYNOPSIS
use ZoneMinder::Control::Trendnet;
place this in /usr/share/perl5/ZoneMinder/Control
=head1 DESCRIPTION
This module contains the implementation of the Trendnet # IP camera control
protocol. Has been tested with TV-IP862IC
Under control capability:
* Main: Can wake, can sleep, can reset
* Move: Can move, can move diagonally, can move mapped, can move relative
* Pan: Can pan
* Tilt: Can tilt
* Presets: Has presets, num presets 20, has home preset (don't set presets via camera's web server, only set via ZM.)
Under control tab in the monitor itself:
Controllable
Control type is the name you gave it in control capability above
Control address is the camera's ip address AND web port. example: 192.168.1.1:80
You can also put the authentication information here and change the
protocol to https using something like https://admin:password@192.168.1.1:80
=head2 EXPORT
None by default.
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2018 ZoneMinder LLC
This library 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 library 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

View File

@ -184,16 +184,19 @@ sub zmDbGetMonitor {
my $id = shift;
return( undef ) if ( !defined($id) );
if ( !defined($id) ) {
croak("Undefined id in zmDbgetMonitor");
return undef ;
}
my $sql = "select * from Monitors where Id = ?";
my $sql = 'SELECT * FROM Monitors WHERE Id = ?';
my $sth = $dbh->prepare_cached($sql)
or croak("Can't prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute($id)
or croak("Can't execute '$sql': ".$sth->errstr());
my $monitor = $sth->fetchrow_hashref();
return( $monitor );
return $monitor;
}
sub zmDbGetMonitorAndControl {

View File

@ -557,8 +557,8 @@ our $hasJSONAny = 0;
sub _testJSON {
return if ( $testedJSON );
my $result = eval {
require JSON::Any;
JSON::Any->import();
require JSON::MaybeXS;
JSON::MaybeXS->import();
};
$testedJSON = 1;
$hasJSONAny = 1 if ( $result );
@ -581,7 +581,7 @@ sub jsonEncode {
_testJSON();
if ( $hasJSONAny ) {
my $string = eval { JSON::Any->objToJson( $value ) };
my $string = eval { JSON::MaybeXS->encode_json( $value ) };
Fatal( "Unable to encode object to JSON: $@" ) unless( $string );
return( $string );
}
@ -616,7 +616,7 @@ sub jsonDecode {
_testJSON();
if ( $hasJSONAny ) {
my $object = eval { JSON::Any->jsonToObj( $value ) };
my $object = eval { JSON::MaybeXS->decode_json( $value ) };
Fatal( "Unable to decode JSON string '$value': $@" ) unless( $object );
return( $object );
}

View File

@ -487,7 +487,7 @@ sub openSyslog {
sub closeSyslog {
my $this = shift;
#closelog();
closelog();
}
sub logFile {
@ -527,20 +527,25 @@ sub logPrint {
my $this = shift;
my $level = shift;
my $string = shift;
my ($caller, undef, $line) = @_ ? @_ : caller;
if ( $level <= $this->{effectiveLevel} ) {
$string =~ s/[\r\n]+$//g;
my $code = $codes{$level};
if ( $level <= $this->{syslogLevel} ) {
syslog($priorities{$level}, $codes{$level}.' [%s]', $string);
}
my ($seconds, $microseconds) = gettimeofday();
if ( $level <= $this->{fileLevel} or $level <= $this->{termLevel} ) {
my $message = sprintf(
'%s.%06d %s[%d].%s [%s]'
'%s.%06d %s[%d].%s [%s:%d] [%s]'
, strftime('%x %H:%M:%S', localtime($seconds))
, $microseconds
, $this->{id}
, $$
, $code
, $codes{$level}
, $caller
, $line
, $string
);
if ( $this->{trace} ) {
@ -548,11 +553,9 @@ sub logPrint {
} else {
$message = $message."\n";
}
if ( $level <= $this->{syslogLevel} ) {
syslog($priorities{$level}, $code.' [%s]', $string);
}
print($LOGFILE $message) if $level <= $this->{fileLevel};
print(STDERR $message) if $level <= $this->{termLevel};
}
if ( $level <= $this->{databaseLevel} ) {
if ( ! ( $this->{dbh} and $this->{dbh}->ping() ) ) {
@ -576,7 +579,7 @@ sub logPrint {
, $this->{id}
, $$
, $level
, $code
, $codes{$level}
, $string
, $this->{fileName}
);
@ -660,39 +663,39 @@ sub Dump {
sub debug {
my $log = shift;
$log->logPrint(DEBUG, @_);
$log->logPrint(DEBUG, @_, caller);
}
sub Debug( @ ) {
fetch()->logPrint(DEBUG, @_);
fetch()->logPrint(DEBUG, @_, caller);
}
sub Info( @ ) {
fetch()->logPrint(INFO, @_);
fetch()->logPrint(INFO, @_, caller);
}
sub info {
my $log = shift;
$log->logPrint(INFO, @_);
$log->logPrint(INFO, @_, caller);
}
sub Warning( @ ) {
fetch()->logPrint(WARNING, @_);
fetch()->logPrint(WARNING, @_, caller);
}
sub warn {
my $log = shift;
$log->logPrint(WARNING, @_);
$log->logPrint(WARNING, @_, caller);
}
sub Error( @ ) {
fetch()->logPrint(ERROR, @_);
fetch()->logPrint(ERROR, @_, caller);
}
sub error {
my $log = shift;
$log->logPrint(ERROR, @_);
$log->logPrint(ERROR, @_, caller);
}
sub Fatal( @ ) {
fetch()->logPrint(FATAL, @_);
fetch()->logPrint(FATAL, @_, caller);
if ( $SIG{TERM} and ( $SIG{TERM} ne 'DEFAULT' ) ) {
$SIG{TERM}();
}
@ -700,7 +703,7 @@ sub Fatal( @ ) {
}
sub Panic( @ ) {
fetch()->logPrint(PANIC, @_);
fetch()->logPrint(PANIC, @_, caller);
confess($_[0]);
}

View File

@ -261,6 +261,7 @@ MAIN: while( $loop ) {
next;
}
my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR );
Debug("Have " . @event_links . ' event links');
closedir(DIR);
my $count = 0;
foreach my $event_link ( @event_links ) {
@ -292,6 +293,42 @@ MAIN: while( $loop ) {
$Event->DiskSpace( undef );
} # event path exists
} # end foreach event_link
# Now check for events that have lost their link
my @time_dirs = glob('[0-9][0-9]/[0-9][0-9]/[0-9][0-9]');
foreach my $event_dir ( @time_dirs ) {
Debug("Checking time dir $event_dir");
( $event_dir ) = ( $event_dir =~ /^(.*)$/ ); # De-taint
my $event_id = undef;
my @mp4_files = glob("$event_dir/[0-9]+\-video.mp4");
foreach my $mp4_file ( @mp4_files ) {
my ( $id ) = $mp4_file =~ /^([0-9]+)\-video\.mp4$/;
if ( $id ) {
$event_id = $id;
last;
}
}
if ( $event_id ) {
my $Event = $fs_events->{$event_id} = new ZoneMinder::Event();
$$Event{Id} = $event_id;
$$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_dir);
$$Event{RelativePath} = join('/', $day_dir, $event_dir);
$$Event{Scheme} = 'Deep';
$Event->MonitorId( $monitor_dir );
$Event->StorageId( $Storage->Id() );
$Event->DiskSpace( undef );
} else {
aud_print("Deleting event directories with no event id information at $day_dir/$event_dir");
if ( confirm() ) {
my $command = "rm -rf $event_dir";
executeShellCommand( $command );
$cleaned = 1;
}
} # end if able to find id
} # end foreach event_dir without link
chdir( $Storage->Path() );
} # end foreach day dir
}
@ -701,17 +738,18 @@ FROM Frames WHERE EventId=?';
if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^(.*)s$/ ) {
$Config{ZM_LOG_DATABASE_LIMIT} = $1;
}
my $deleted_rows;
do {
my $deleteLogByTimeSql =
'DELETE low_priority FROM Logs
WHERE TimeKey < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.')';
'DELETE FROM Logs
WHERE TimeKey < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.') LIMIT 10';
my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql )
or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() );
$res = $deleteLogByTimeSth->execute()
or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() );
if ( $deleteLogByTimeSth->rows() ){
aud_print( 'Deleted '.$deleteLogByTimeSth->rows()
." log table entries by time\n" );
}
$deleted_rows = $deleteLogByTimeSth->rows();
aud_print( "Deleted $deleted_rows log table entries by time\n" );
} while ( $deleted_rows );
}
} # end if ZM_LOG_DATABASE_LIMIT
$loop = $continuous;

View File

@ -21,32 +21,6 @@
#
# ==========================================================================
=head1 NAME
zmcontrol.pl - ZoneMinder control script
=head1 SYNOPSIS
zmcontrol.pl --id {monitor_id} --command={command} [various options]
=head1 DESCRIPTION
FIXME FIXME
=head1 OPTIONS
--autostop -
--xcoord [ arg ] - X-coord
--ycoord [ arg ] - Y-coord
--speed [ arg ] - Speed
--step [ arg ] -
--panspeed [ arg ] -
--panstep [ arg ] -
--tiltspeed [ arg ] -
--tiltstep [ arg ] -
--preset [ arg ] -
=cut
use strict;
@EXTRA_PERL_LIB@
@ -58,7 +32,7 @@ use Socket;
#use Data::Dumper;
use Module::Load::Conditional qw{can_load};;
use constant MAX_CONNECT_DELAY => 10;
use constant MAX_CONNECT_DELAY => 15;
use constant MAX_COMMAND_WAIT => 1800;
$| = 1;
@ -89,15 +63,14 @@ GetOptions(
'autostop' =>\$options{autostop},
) or pod2usage(-exitstatus => -1);
if ( !$id || !$options{command} )
{
if ( !$id || !$options{command} ) {
print( STDERR "Please give a valid monitor id and command\n" );
pod2usage(-exitstatus => -1);
}
( $id ) = $id =~ /^(\w+)$/;
Debug( $arg_string );
Debug("zmcontrol: arg string: $arg_string");
my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock';
@ -106,33 +79,28 @@ socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 )
my $saddr = sockaddr_un($sock_file);
my $server_up = connect(CLIENT, $saddr);
if ( !$server_up )
{
if ( !$server_up ) {
# The server isn't there
my $monitor = zmDbGetMonitorAndControl($id);
if ( !$monitor )
{
if ( !$monitor ) {
Fatal("Unable to load control data for monitor $id");
}
my $protocol = $monitor->{Protocol};
if ( -x $protocol )
{
if ( -x $protocol ) {
# Protocol is actually a script!
# Holdover from previous versions
my $command .= $protocol.' '.$arg_string;
Debug( $command."\n" );
Debug($command);
my $output = qx($command);
my $status = $? >> 8;
if ( $status || logDebugging() )
{
if ( $status || logDebugging() ) {
chomp($output);
Debug( "Output: $output\n" );
Debug("Output: $output");
}
if ( $status )
{
Error( "Command '$command' exited with status: $status\n" );
if ( $status ) {
Error("Command '$command' exited with status: $status");
exit($status);
}
exit(0);
@ -142,26 +110,22 @@ if ( !$server_up )
close(CLIENT);
if ( ! can_load( modules => { "ZoneMinder::Control::$protocol" => undef } ) ) {
Fatal("Can't load ZoneMinder::Control::$protocol");
Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR");
}
if ( my $cpid = fork() )
{
if ( my $cpid = fork() ) {
logReinit();
# Parent process just sleep and fall through
socket(CLIENT, PF_UNIX, SOCK_STREAM, 0)
or die("Can't open socket: $!");
my $attempts = 0;
while (!connect( CLIENT, $saddr ))
{
while ( !connect(CLIENT, $saddr) ) {
$attempts++;
Fatal( "Can't connect: $! after $attempts attempts to $sock_file" ) if ($attempts > MAX_CONNECT_DELAY);
Fatal("Can't connect: $! after $attempts attempts to $sock_file") if $attempts > MAX_CONNECT_DELAY;
sleep(1);
}
}
elsif ( defined($cpid) )
{
} elsif ( defined($cpid) ) {
close(STDOUT);
close(STDERR);
@ -192,17 +156,14 @@ if ( !$server_up )
my $win = $rin;
my $ein = $win;
my $timeout = MAX_COMMAND_WAIT;
while( 1 )
{
while( 1 ) {
my $nfound = select(my $rout = $rin, undef, undef, $timeout);
if ( $nfound > 0 )
{
if ( vec( $rout, fileno(SERVER), 1 ) )
{
if ( $nfound > 0 ) {
if ( vec( $rout, fileno(SERVER), 1 ) ) {
my $paddr = accept(CLIENT, SERVER);
my $message = <CLIENT>;
next if ( !$message );
next if !$message;
my $params = jsonDecode($message);
#Debug( Dumper( $params ) );
@ -213,41 +174,28 @@ if ( !$server_up )
last;
}
$control->$command($params);
} else {
Fatal('Bogus descriptor');
}
else
{
Fatal( "Bogus descriptor" );
}
}
elsif ( $nfound < 0 )
{
if ( $! == EPIPE )
{
} elsif ( $nfound < 0 ) {
if ( $! == EPIPE ) {
Error("Can't select: $!");
}
else
{
} else {
Fatal("Can't select: $!");
}
}
else
{
} else {
#print( "Select timed out\n" );
last;
}
}
Info( "Control server $id/$protocol exiting at "
.strftime( '%y/%m/%d %H:%M:%S', localtime() )
);
} # end while forever
Info("Control server $id/$protocol exiting");
unlink($sock_file);
$control->close();
exit(0);
}
else
{
} else {
Fatal("Can't fork: $!");
}
}
} # end if !server up
# The server is there, connect to it
#print( "Writing commands\n" );
@ -258,3 +206,33 @@ print( CLIENT $message );
shutdown(CLIENT, 1);
exit(0);
1;
__END__
=head1 NAME
zmcontrol.pl - ZoneMinder control script
=head1 SYNOPSIS
zmcontrol.pl --id {monitor_id} --command={command} [various options]
=head1 DESCRIPTION
FIXME FIXME
=head1 OPTIONS
--autostop -
--xcoord [ arg ] - X-coord
--ycoord [ arg ] - Y-coord
--speed [ arg ] - Speed
--step [ arg ] -
--panspeed [ arg ] -
--panstep [ arg ] -
--tiltspeed [ arg ] -
--tiltstep [ arg ] -
--preset [ arg ] -
=cut

View File

@ -370,7 +370,6 @@ sub run {
restartPending();
check_for_processes_to_kill() if %terminating_processes;
reaper() if %pids_to_reap;
} # end while
dPrint(ZoneMinder::Logger::INFO, 'Server exiting at '
@ -437,7 +436,7 @@ sub start {
$dbh = zmDbConnect(1);
# This logReinit is required. Not sure why.
logReinit();
#logReinit();
$process->{pid} = $cpid;
$process->{started} = time();
@ -647,7 +646,6 @@ sub chld_sig_handler {
sub reaper {
foreach my $cpid ( keys %pids_to_reap ) {
my $process = $pid_hash{$cpid};
Debug("Reaping pricess $cpid");
delete $pid_hash{$cpid};
my $reap_info = $pids_to_reap{$cpid};
my ( $status, $stopped ) = @$reap_info{'status','stopped'};
@ -713,7 +711,6 @@ Debug("Reaping pricess $cpid");
} else {
delete $cmd_hash{$$process{command}};
}
Debug("Reaping pricess $cpid");
} # end foreach pid_to_reap
} # end sub reaper

View File

@ -70,15 +70,17 @@ use Getopt::Long;
use autouse 'Pod::Usage'=>qw(pod2usage);
use autouse 'Data::Dumper'=>qw(Dumper);
my $daemon = 0;
my $filter_name = '';
my $filter_id;
my $version = 0;
my $zm_terminate = 0;
GetOptions(
daemon =>\$daemon,
'filter=s' =>\$filter_name,
'filter_id=s' =>\$filter_id,
'version' =>\$version
version =>\$version
) or pod2usage(-exitstatus => -1);
if ( $version ) {
@ -96,11 +98,17 @@ use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|)
logInit($filter_id?(id=>'zmfilter_'.$filter_id):());
sub HupHandler {
Info("Received HUP, reloading");
# This idea at this time is to just exit, freeing up the memory.
# zmfilter.pl will be respawned by zmdc.
TermHandler();
return;
Info('Received HUP, reloading');
ZoneMinder::Object::init_cache();
&ZoneMinder::Logger::logHupHandler();
}
sub TermHandler {
Info("Received TERM, exiting");
Info('Received TERM, exiting');
$zm_terminate = 1;
}
sub Term {
@ -173,7 +181,7 @@ if ( $filter_name ) {
}
if ( ! ( $filter_name or $filter_id ) ) {
Debug("Sleeping due to start delay: " . START_DELAY . ' seconds...');
Debug('Sleeping due to start delay: ' . START_DELAY . ' seconds...');
sleep(START_DELAY);
}
@ -201,7 +209,7 @@ while( !$zm_terminate ) {
}
}
last if $filter_name or $filter_id or $zm_terminate;
last if (!$daemon and ($filter_name or $filter_id)) or $zm_terminate;
Debug("Sleeping for $delay seconds\n");
sleep($delay);
@ -266,7 +274,6 @@ sub checkFilter {
join(' ',
'Checking filter', $filter->{Name},
join(', ',
($filter->{AutoDelete}?'delete':()),
($filter->{AutoArchive}?'archive':()),
($filter->{AutoVideo}?'video':()),

View File

@ -41,9 +41,10 @@ use autouse 'Pod::Usage'=>qw(pod2usage);
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
my $store_state=""; # PP - will remember state name passed
my $store_state=''; # PP - will remember state name passed
logInit();
Info("Aftere LogInit");
my $command = $ARGV[0]||'';
if ( $command eq 'version' ) {
@ -53,11 +54,10 @@ if ( $command eq 'version' ) {
my $state;
my $dbh;
my $dbh = zmDbConnect();
Info("Command: $command");
if ( !$command || $command !~ /^(?:start|stop|restart|status|logrot|version)$/ ) {
if ( $command ) {
$dbh = zmDbConnect();
# Check to see if it's a valid run state
my $sql = 'SELECT * FROM States WHERE Name=?';
my $sth = $dbh->prepare_cached($sql)
@ -65,10 +65,10 @@ if ( !$command || $command !~ /^(?:start|stop|restart|status|logrot|version)$/ )
my $res = $sth->execute($command)
or Fatal("Can't execute: ".$sth->errstr());
if ( $state = $sth->fetchrow_hashref() ) {
$state->{Name} = $command;
#$state->{Name} = $command;
$state->{Definitions} = [];
foreach( split( /,/, $state->{Definition} ) ) {
my ( $id, $function, $enabled ) = split( /:/, $_ );
foreach( split(',', $state->{Definition}) ) {
my ( $id, $function, $enabled ) = split(':', $_);
push( @{$state->{Definitions}},
{ Id=>$id, Function=>$function, Enabled=>$enabled }
);
@ -82,24 +82,26 @@ if ( !$command || $command !~ /^(?:start|stop|restart|status|logrot|version)$/ )
if ( !$command ) {
pod2usage(-exitstatus => -1);
}
}
$dbh = zmDbConnect() if ! $dbh;
} # end if not one of the usual commands
# PP - Sane state check
Debug("StartisActiveSSantiyCheck");
isActiveSanityCheck();
Debug("Done isActiveSSantiyCheck");
# Move to the right place
chdir($Config{ZM_PATH_WEB})
or Fatal( "Can't chdir to '".$Config{ZM_PATH_WEB}."': $!" );
or Fatal("Can't chdir to '$Config{ZM_PATH_WEB}': $!");
my $dbg_id = '';
Info( "Command: $command\n" );
Info("Command: $command");
my $retval = 0;
if ( $command eq 'state' ) {
Info( "Updating DB: $state->{Name}\n" );
my $sql = $Config{ZM_SERVER_ID} ? 'SELECT * FROM Monitors WHERE ServerId=? ORDER BY Id ASC' : 'SELECT * FROM Monitors ORDER BY Id ASC';
Info("Updating DB: $state->{Name}");
my $sql = 'SELECT * FROM Monitors' . ($Config{ZM_SERVER_ID} ? ' WHERE ServerId=?' : '' ) .' ORDER BY Id ASC';
my $sth = $dbh->prepare_cached($sql)
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute($Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID}: ())
@ -124,14 +126,14 @@ chdir( $Config{ZM_PATH_WEB} )
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute($monitor->{NewFunction}, $monitor->{NewEnabled}, $monitor->{Id})
or Fatal("Can't execute: ".$sth->errstr());
}
}
} # end if change of function or enablement
} # end foreach monitor
$sth->finish();
# PP - Now mark a specific state as active
resetStates();
Info("Marking $store_state as Enabled");
$sql = "UPDATE States SET IsActive = '1' WHERE Name = ?";
$sql = 'UPDATE States SET IsActive = 1 WHERE Name = ?';
$sth = $dbh->prepare_cached($sql)
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
$res = $sth->execute($store_state)
@ -139,7 +141,7 @@ chdir( $Config{ZM_PATH_WEB} )
# PP - zero out other states isActive
$command = 'restart';
}
} # end if command = state
# Check if we are running systemd and if we have been called by the system
if ( $command =~ /^(start|stop|restart)$/ ) {
@ -154,6 +156,7 @@ if ( $command =~ /^(start|stop|restart)$/ ) {
if ( $command =~ /^(?:stop|restart)$/ ) {
my $status = runCommand('zmdc.pl check');
Debug("zmdc.pl check = $status");
if ( $status eq 'running' ) {
runCommand('zmdc.pl shutdown');
@ -163,10 +166,9 @@ if ( $command =~ /^(?:stop|restart)$/ ) {
}
}
#runCommand( "zmupdate.pl -f" );
if ( $command =~ /^(?:start|restart)$/ ) {
my $status = runCommand('zmdc.pl check');
Debug("zmdc.pl check = $status");
if ( $status eq 'stopped' ) {
if ( $Config{ZM_DYN_DB_VERSION}
@ -196,7 +198,7 @@ if ( $command =~ /^(?:start|restart)$/ ) {
my @values;
if ( $Config{ZM_SERVER_ID} ) {
require ZoneMinder::Server;
Info("Multi-server configuration detected. Starting up services for server $Config{ZM_SERVER_ID}\n");
Info("Multi-server configuration detected. Starting up services for server $Config{ZM_SERVER_ID}");
$Server = new ZoneMinder::Server($Config{ZM_SERVER_ID});
$sql = 'SELECT * FROM Monitors WHERE ServerId=?';
@values = ( $Config{ZM_SERVER_ID} );
@ -221,16 +223,19 @@ if ( $command =~ /^(?:start|restart)$/ ) {
runCommand("zmdc.pl start zma -m $monitor->{Id}");
}
if ( $Config{ZM_OPT_CONTROL} ) {
if ( $monitor->{Function} eq 'Modect' || $monitor->{Function} eq 'Mocord' ) {
if ( $monitor->{Controllable} && $monitor->{TrackMotion} ) {
if ( $monitor->{Function} eq 'Modect' || $monitor->{Function} eq 'Mocord' ) {
runCommand( "zmdc.pl start zmtrack.pl -m $monitor->{Id}" );
}
}
}
}
}
} else {
Warning(' Monitor is set to track motion, but does not have motion detection enabled.');
} # end if Has motion enabled
} # end if track motion
} # end if ZM_OPT_CONTROL
} # end if function is not none or Website
} # end foreach monitor
$sth->finish();
}
{
my $sql = 'SELECT Id FROM Filters WHERE Background=1';
my $sth = $dbh->prepare_cached($sql)
@ -240,7 +245,7 @@ if ( $command =~ /^(?:start|restart)$/ ) {
if ( $sth->rows ) {
while( my $filter = $sth->fetchrow_hashref() ) {
# This is now started unconditionally
runCommand("zmdc.pl start zmfilter.pl --filter_id=$$filter{Id}");
runCommand("zmdc.pl start zmfilter.pl --filter_id=$$filter{Id} --daemon");
}
} else {
runCommand('zmdc.pl start zmfilter.pl');
@ -283,7 +288,7 @@ if ( $command =~ /^(?:start|restart)$/ ) {
} else {
$retval = 1;
}
}
} # end if command is start or restart
if ( $command eq 'status' ) {
my $status = runCommand('zmdc.pl check');
@ -302,7 +307,7 @@ sub isActiveSanityCheck {
$dbh = zmDbConnect() if ! $dbh;
# PP - First, make sure default exists and there is only one
my $sql = "SELECT Name FROM States WHERE Name='default'";
my $sql = q`SELECT Name FROM States WHERE Name='default'`;
my $sth = $dbh->prepare_cached($sql)
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute()
@ -316,16 +321,15 @@ sub isActiveSanityCheck {
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
$res = $sth->execute()
or Fatal( "Can't execute: ".$sth->errstr() );
$sql = "INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');";
$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'";
$sql = 'SELECT Name FROM States WHERE IsActive = 1';
$sth = $dbh->prepare_cached($sql)
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
$res = $sth->execute()
@ -334,51 +338,36 @@ sub isActiveSanityCheck {
if ( $sth->rows != 1 ) {
Info('Fixing States table so only one run state is active');
resetStates();
$sql = "UPDATE States SET IsActive='1' WHERE Name='default'";
$sql = q`UPDATE States SET IsActive=1 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());
}
}
} # end sub isActiveSanityCheck
# PP - zeroes out isActive for all states
sub resetStates {
$dbh = zmDbConnect() if ! $dbh;
my $sql = "UPDATE States SET IsActive='0'";
my $sql = 'UPDATE States SET IsActive=0';
my $sth = $dbh->prepare_cached($sql)
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute()
or Fatal("Can't execute: ".$sth->errstr());
}
sub systemdRunning {
my $result = 0;
my $output = qx(ps -o comm="" -p 1);
chomp( $output );
if ( $output =~ /systemd/ ) {
$result = 1;
}
return $result;
return scalar ( $output =~ /systemd/ );
}
sub calledBysystem {
my $result = 0;
my $ppid = getppid();
my $output = qx(ps -o comm="" -p $ppid);
chomp( $output );
#chomp( $output );
if ($output =~ /^(?:systemd|init)$/) {
$result = 1;
}
return $result;
return ($output =~ /^(?:systemd|init)$/);
}
sub verifyFolder {
@ -394,16 +383,14 @@ sub verifyFolder {
# Not running as web user, so should be root in which case
# chown the directory
my ( $webName, $webPass, $webUid, $webGid ) = getpwnam($Config{ZM_WEB_USER})
or Fatal( "Can't get user details for web user '"
.$Config{ZM_WEB_USER}."': $!"
or Fatal("Can't get details for web user '$Config{ZM_WEB_USER}': $!");
chown($webUid, $webGid, $folder)
or Fatal("Can't change ownership of '$folder' to '"
.$Config{ZM_WEB_USER}.':'.$Config{ZM_WEB_GROUP}."': $!"
);
chown( $webUid, $webGid, "$folder" )
or Fatal( "Can't change ownership of directory '$folder' to '"
.$Config{ZM_WEB_USER}.":".$Config{ZM_WEB_GROUP}."': $!"
);
}
}
}
} # end if runName ne ZM_WEB_USER
} # end if folder doesn't exist
} # end sub verifyFolder
1;
__END__

View File

@ -33,6 +33,7 @@ use LWP::UserAgent;
use Sys::MemInfo qw(totalmem);
use Sys::CPU qw(cpu_count);
use POSIX qw(strftime uname);
use JSON::MaybeXS;
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
@ -87,7 +88,7 @@ while( 1 ) {
# We should keep *BSD systems in mind when calling system commands
my %telemetry;
$telemetry{uuid} = getUUID($dbh);
$telemetry{ip} = getIP();
($telemetry{city}, $telemetry{region}, $telemetry{country}, $telemetry{latitude}, $telemetry{longitude}) = getGeo();
$telemetry{timestamp} = strftime( '%Y-%m-%dT%H:%M:%S%z', localtime() );
$telemetry{monitor_count} = countQuery($dbh,'Monitors');
$telemetry{event_count} = countQuery($dbh,'Events');
@ -203,22 +204,25 @@ sub getUUID {
return $uuid;
}
# Retrieves the local server's external IP address
sub getIP {
my $ipaddr = '0.0.0.0';
# Retrieve this server's general location information from a GeoIP database
sub getGeo {
my $unknown = 'Unknown';
my $endpoint = 'https://ipinfo.io/geo';
my $ua = LWP::UserAgent->new;
my $server_endpoint = 'https://wiki.zoneminder.com/ip.php';
my $req = HTTP::Request->new(GET => $server_endpoint);
my $req = HTTP::Request->new(GET => $endpoint);
my $resp = $ua->request($req);
my $resp_msg = $resp->decoded_content;
my $resp_code = $resp->code;
if ($resp->is_success) {
$ipaddr = $resp->decoded_content;
my $content = decode_json( $resp_msg );
(my $latitude, my $longitude) = split /,/, $content->{loc};
return ($content->{city}, $content->{region}, $content->{country}, $latitude, $longitude);
} else {
Warning("Geoip data retrieval returned HTTP POST error code: $resp_code");
Debug("Geoip data retrieval failure response message: $resp_msg");
return ($unknown, $unknown, $unknown, $unknown);
}
Debug("Found external ip address of: $ipaddr");
return $ipaddr;
}
# As the name implies, just your average mysql count query

View File

@ -173,6 +173,12 @@ my $cwd = getcwd;
my $video_name;
my @event_ids;
# Fail if the path to a valid ffmpeg binary is not set
if ( ! -x $Config{ZM_PATH_FFMPEG} ) {
Fatal("Ffmpeg binary not found or not executable. Verify ZM_PATH_FFMPEG points to ffmpeg, avconv, or a compatible binary.");
}
if ( $event_id ) {
@event_ids = ( $event_id );

View File

@ -123,9 +123,9 @@ void process_configfile( char* configFile) {
if ( *line_ptr == '\0' || *line_ptr == '#' )
continue;
// Remove trailing white space
// Remove trailing white space and trailing quotes
char *temp_ptr = line_ptr+strlen(line_ptr)-1;
while ( *temp_ptr == ' ' || *temp_ptr == '\t' ) {
while ( *temp_ptr == ' ' || *temp_ptr == '\t' || *temp_ptr == '\'' || *temp_ptr == '\"') {
*temp_ptr-- = '\0';
temp_ptr--;
}
@ -147,8 +147,9 @@ void process_configfile( char* configFile) {
temp_ptr--;
} while ( *temp_ptr == ' ' || *temp_ptr == '\t' );
// Remove leading white space from the value part
// Remove leading white space and leading quotes from the value part
white_len = strspn( val_ptr, " \t" );
white_len += strspn( val_ptr, "\'\"" );
val_ptr += white_len;
if ( strcasecmp( name_ptr, "ZM_DB_HOST" ) == 0 )

View File

@ -594,11 +594,12 @@ Debug(3, "Writing video");
max_score = score;
if ( alarm_image ) {
snprintf(event_file, sizeof(event_file), staticConfig.analyse_file_format, path, frames);
Debug(1, "Writing analysis frame %d", frames);
if ( monitor->GetOptSaveJPEGs() & 2 ) {
WriteFrameImage(alarm_image, timestamp, event_file, true);
snprintf(event_file, sizeof(event_file), staticConfig.analyse_file_format, path, frames);
Debug(1, "Writing analysis frame %d", frames);
if ( ! WriteFrameImage(alarm_image, timestamp, event_file, true) ) {
Error("Failed to write analysis frame image");
}
}
}
}

View File

@ -540,7 +540,13 @@ void Logger::logPrint( bool hex, const char * const filepath, const int line, co
if ( ! db_mutex.trylock() ) {
mysql_real_escape_string( &dbconn, escapedString, syslogStart, strlen(syslogStart) );
snprintf( sql, sizeof(sql), "insert into Logs ( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line ) values ( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line );
snprintf(sql, sizeof(sql),
"INSERT INTO Logs "
"( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line )"
" VALUES "
"( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )",
timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line
);
if ( mysql_query(&dbconn, sql) ) {
Level tempDatabaseLevel = mDatabaseLevel;
databaseLevel(NOLOG);

View File

@ -1234,7 +1234,7 @@ bool Monitor::CheckSignal( const Image *image ) {
bool Monitor::Analyse() {
if ( shared_data->last_read_index == shared_data->last_write_index ) {
// I wonder how often this happens. Maybe if this happens we should sleep or something?
return( false );
return false;
}
struct timeval now;
@ -1547,7 +1547,7 @@ bool Monitor::Analyse() {
if ( score ) {
if ( state == IDLE || state == TAPE || state == PREALARM ) {
if ( (!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count) ) {
Info("%s: %03d - Gone into alarm state %u > %u",
Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u",
name, image_count, Event::PreAlarmCount(), alarm_frame_count);
shared_data->state = state = ALARM;
if ( signal_change || (function != MOCORD && state != ALERT) ) {
@ -1576,6 +1576,9 @@ bool Monitor::Analyse() {
else
pre_index = ((index + image_buffer_count) - pre_event_count)%image_buffer_count;
Debug(4,"Resulting pre_index(%d) from index(%d) + image_buffer_count(%d) - pre_event_count(%d) % %d",
pre_index, index, image_buffer_count, pre_event_count, image_buffer_count);
// Seek forward the next filled slot in to the buffer (oldest data)
// from the current position
while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) {

View File

@ -55,6 +55,10 @@ RemoteCamera::~RemoteCamera() {
freeaddrinfo(hp);
hp = NULL;
}
if ( mAuthenticator ) {
delete mAuthenticator;
mAuthenticator = NULL;
}
}
void RemoteCamera::Initialise() {

View File

@ -33,8 +33,7 @@ Authenticator::Authenticator( const std::string &username, const std::string &pa
{
#ifdef HAVE_GCRYPT_H
// Special initialisation for libgcrypt
if ( !gcry_check_version( GCRYPT_VERSION ) )
{
if ( !gcry_check_version(GCRYPT_VERSION) ) {
Fatal("Unable to initialise libgcrypt");
}
gcry_control( GCRYCTL_DISABLE_SECMEM, 0 );
@ -64,20 +63,17 @@ void Authenticator::authHandleHeader(std::string headerData)
size_t digest_match_len = strlen(digest_match);
// Check if basic auth
if (strncasecmp(headerData.c_str(),basic_match,strlen(basic_match)) == 0)
{
if ( strncasecmp(headerData.c_str(),basic_match,strlen(basic_match)) == 0 ) {
fAuthMethod = AUTH_BASIC;
Debug(2, "Set authMethod to Basic");
}
// Check if digest auth
else if (strncasecmp( headerData.c_str(),digest_match,digest_match_len ) == 0)
{
else if (strncasecmp( headerData.c_str(),digest_match,digest_match_len ) == 0) {
fAuthMethod = AUTH_DIGEST;
Debug( 2, "Set authMethod to Digest");
StringVector subparts = split(headerData.substr(digest_match_len, headerData.length() - digest_match_len), ",");
// subparts are key="value"
for ( size_t i = 0; i < subparts.size(); i++ )
{
for ( size_t i = 0; i < subparts.size(); i++ ) {
StringVector kvPair = split(trimSpaces(subparts[i]), "=");
std::string key = trimSpaces(kvPair[0]);
if ( key == "realm" ) {
@ -93,7 +89,8 @@ void Authenticator::authHandleHeader(std::string headerData)
continue;
}
}
Debug( 2, "Auth data completed. User: %s, realm: %s, nonce: %s, qop: %s", username().c_str(), fRealm.c_str(), fNonce.c_str(), fQop.c_str() );
Debug(2, "Auth data completed. User: %s, realm: %s, nonce: %s, qop: %s",
username().c_str(), fRealm.c_str(), fNonce.c_str(), fQop.c_str());
}
}
@ -103,12 +100,9 @@ std::string Authenticator::quote( const std::string &src ) {
std::string Authenticator::getAuthHeader(std::string method, std::string uri) {
std::string result = "Authorization: ";
if (fAuthMethod == AUTH_BASIC)
{
if (fAuthMethod == AUTH_BASIC) {
result += "Basic " + base64Encode( username() + ":" + password() );
}
else if (fAuthMethod == AUTH_DIGEST)
{
} else if (fAuthMethod == AUTH_DIGEST) {
result += std::string("Digest ") +
"username=\"" + quote(username()) + "\", realm=\"" + quote(realm()) + "\", " +
"nonce=\"" + quote(nonce()) + "\", uri=\"" + quote(uri) + "\"";
@ -153,8 +147,7 @@ std::string Authenticator::computeDigestResponse(std::string &method, std::strin
gnutls_datum_t md5dataha1 = { (unsigned char*)ha1Data.c_str(), ha1Data.length() };
gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha1, md5buf, &md5len );
#endif
for ( unsigned int j = 0; j < md5len; j++ )
{
for ( unsigned int j = 0; j < md5len; j++ ) {
sprintf(&md5HexBuf[2*j], "%02x", md5buf[j] );
}
md5HexBuf[md5len*2]='\0';
@ -169,8 +162,7 @@ std::string Authenticator::computeDigestResponse(std::string &method, std::strin
gnutls_datum_t md5dataha2 = { (unsigned char*)ha2Data.c_str(), ha2Data.length() };
gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha2, md5buf, &md5len );
#endif
for ( unsigned int j = 0; j < md5len; j++ )
{
for ( unsigned int j = 0; j < md5len; j++ ) {
sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] );
}
md5HexBuf[md5len*2]='\0';
@ -191,8 +183,7 @@ std::string Authenticator::computeDigestResponse(std::string &method, std::strin
gnutls_datum_t md5datadigest = { (unsigned char*)digestData.c_str(), digestData.length() };
gnutls_fingerprint( GNUTLS_DIG_MD5, &md5datadigest, md5buf, &md5len );
#endif
for ( unsigned int j = 0; j < md5len; j++ )
{
for ( unsigned int j = 0; j < md5len; j++ ) {
sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] );
}
md5HexBuf[md5len*2]='\0';
@ -200,7 +191,7 @@ std::string Authenticator::computeDigestResponse(std::string &method, std::strin
return md5HexBuf;
#else // HAVE_DECL_MD5
Error("You need to build with gnutls or openssl installed to use digest authentication");
return( 0 );
return 0;
#endif // HAVE_DECL_MD5
}

View File

@ -236,7 +236,9 @@ bool Zone::CheckAlarms(const Image *delta_image) {
if ( pixel_diff_count && alarm_pixels )
pixel_diff = pixel_diff_count/alarm_pixels;
Debug(5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d", alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff);
Debug(5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d",
alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff);
if ( alarm_pixels ) {
if ( min_alarm_pixels && (alarm_pixels < (unsigned int)min_alarm_pixels) ) {
@ -312,7 +314,8 @@ bool Zone::CheckAlarms(const Image *delta_image) {
if ( config.record_diag_images )
diff_image->WriteJpeg(diag_path);
Debug(5, "Got %d filtered pixels, need %d -> %d", alarm_filter_pixels, min_filter_pixels, max_filter_pixels);
Debug(5, "Got %d filtered pixels, need %d -> %d",
alarm_filter_pixels, min_filter_pixels, max_filter_pixels);
if ( alarm_filter_pixels ) {
if ( min_filter_pixels && (alarm_filter_pixels < min_filter_pixels) ) {
@ -328,7 +331,7 @@ bool Zone::CheckAlarms(const Image *delta_image) {
return false;
}
score = (100*alarm_filter_pixels)/(polygon.Area());
score = (100*alarm_filter_pixels)/polygon.Area();
if ( score < 1 )
score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */
Debug(5, "Current score is %d", score);
@ -438,7 +441,8 @@ bool Zone::CheckAlarms(const Image *delta_image) {
alarm_blobs--;
Debug(6, "Merging blob %d with %d at %d,%d, %d current blobs", bss->tag, bsm->tag, x, y, alarm_blobs);
Debug(6, "Merging blob %d with %d at %d,%d, %d current blobs",
bss->tag, bsm->tag, x, y, alarm_blobs);
// Clear out the old blob
bss->tag = 0;
@ -476,7 +480,11 @@ bool Zone::CheckAlarms(const Image *delta_image) {
BlobStats *bs = &blob_stats[i];
// See if we can recycle one first, only if it's at least two rows up
if ( bs->count && bs->hi_y < (int)(y-1) ) {
if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) {
if (
(min_blob_pixels && bs->count < min_blob_pixels)
||
(max_blob_pixels && bs->count > max_blob_pixels)
) {
if ( config.create_analysis_images || config.record_diag_images ) {
for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) {
spdiff = diff_buff + ((diff_width * sy) + bs->lo_x);

View File

@ -366,5 +366,5 @@ int main(int argc, char *argv[]) {
logTerm();
zmDbClose();
return result;
return zm_terminate ? 0 : result;
}

View File

@ -263,7 +263,7 @@ execpackpack () {
packpack/packpack $parms | grep -Ev '^(-- Installing:|-- Up-to-date:|Skip blib|Manifying|Installing /build|cp lib|writing output...|copying images...|reading sources...|[Working])'
else
# Travis never ceases to amaze. For the case of arm emulation, Travis fails the build due to too little output over a 10 minute period. Facepalm.
packpack/packpack $parms | grep -Ev '^(-- Installing:|-- Up-to-date:|Skip blib|Installing /build|cp lib|writing output...|copying images...|reading sources...|[Working])'
packpack/packpack $parms | grep -Ev '^(-- Installing:|Skip blib|Manifying|Installing /build|cp lib|writing output...|copying images...|reading sources...|[Working])'
fi
else
packpack/packpack $parms

View File

@ -1 +1 @@
1.31.45
1.31.47

View File

@ -59,7 +59,7 @@ switch ( $_REQUEST['task'] ) {
$sortField = $_REQUEST['sortField'];
}
}
$sortOrder = (isset($_REQUEST['sortOrder']) and $_REQUEST['sortOrder']) == 'asc' ? 'asc':'desc';
$sortOrder = (isset($_REQUEST['sortOrder']) and ($_REQUEST['sortOrder'] == 'asc')) ? 'asc' : 'desc';
$filter = isset($_REQUEST['filter']) ? $_REQUEST['filter'] : array();
$total = dbFetchOne('SELECT count(*) AS Total FROM Logs', 'Total');
@ -93,7 +93,10 @@ switch ( $_REQUEST['task'] ) {
$sql .= ' ORDER BY '.$sortField.' '.$sortOrder.' LIMIT '.$limit;
$logs = array();
foreach ( dbFetchAll($sql, NULL, $values) as $log ) {
$log['DateTime'] = preg_replace('/^\d+/', strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])), $log['TimeKey']);
$log['DateTime'] = strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey']));
#Warning("TimeKey: " . $log['TimeKey'] . 'Intval:'.intval($log['TimeKey']).' DateTime:'.$log['DateTime']);
#$log['DateTime'] = preg_replace('/^\d+/', strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])), $log['TimeKey']);
$log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : '';
$log['Message'] = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $log['Message']);
foreach( $filterFields as $field ) {
@ -347,7 +350,7 @@ switch ( $_REQUEST['task'] ) {
</logexport>' );
break;
}
$exportExt = "xml";
$exportExt = 'xml';
break;
}
fclose( $exportFP );
@ -363,10 +366,10 @@ switch ( $_REQUEST['task'] ) {
ajaxError('Insufficient permissions to download logs');
if ( empty($_REQUEST['key']) )
Fatal( "No log export key given" );
Fatal('No log export key given');
$exportKey = $_REQUEST['key'];
if ( empty($_REQUEST['format']) )
Fatal( "No log export format given" );
Fatal('No log export format given');
$format = $_REQUEST['format'];
switch( $format ) {
@ -389,15 +392,15 @@ switch ( $_REQUEST['task'] ) {
$exportFile = "zm-log.$exportExt";
$exportPath = ZM_PATH_SWAP."/zm-log-$exportKey.$exportExt";
header( "Pragma: public" );
header( "Expires: 0" );
header( "Cache-Control: must-revalidate, post-check=0, pre-check=0" );
header( "Cache-Control: private", false ); // required by certain browsers
header( "Content-Description: File Transfer" );
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: private', false ); // required by certain browsers
header('Content-Description: File Transfer');
header('Content-Disposition: attachment; filename="'.$exportFile.'"' );
header( "Content-Transfer-Encoding: binary" );
header( "Content-Type: application/force-download" );
header( "Content-Length: ".filesize($exportPath) );
header('Content-Transfer-Encoding: binary');
header('Content-Type: application/force-download');
header('Content-Length: '.filesize($exportPath));
readfile($exportPath);
exit(0);
break;

View File

@ -60,81 +60,47 @@ class AppController extends Controller {
// for role and deny API access in future
// Also checking to do this only if ZM_OPT_USE_AUTH is on
public function beforeFilter() {
$this->loadModel('Config');
$options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_API'));
$config = $this->Config->find('first', $options);
$zmOptApi = $config['Config']['Value'];
if ($zmOptApi !='1') {
if ( ! ZM_OPT_USE_API ) {
throw new UnauthorizedException(__('API Disabled'));
return;
}
$options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_AUTH'));
$config = $this->Config->find('first', $options);
$zmOptAuth = $config['Config']['Value'];
# For use throughout the app. If not logged in, this will be null.
global $user;
$user = $this->Session->read('user');
if ( $zmOptAuth == '1' ) {
require_once "../../../includes/auth.php";
if ( ZM_OPT_USE_AUTH ) {
require_once '../../../includes/auth.php';
$this->loadModel('User');
if ( isset($_REQUEST['user']) and isset($_REQUEST['pass']) ) {
$user = $this->User->find('first', array ('conditions' => array (
'User.Username' => $_REQUEST['user'],
'User.Password' => $_REQUEST['pass'],
)) );
$mUser = $this->request->query('user') ? $this->request->query('user') : $this->request->data('user');
$mPassword = $this->request->query('pass') ? $this->request->query('pass') : $this->request->data('pass');
$mAuth = $this->request->query('auth') ? $this->request->query('auth') : $this->request->data('auth');
if ( $mUser and $mPassword ) {
$user = userLogin($mUser, $mPassword);
if ( !$user ) {
throw new UnauthorizedException(__('User not found'));
throw new UnauthorizedException(__('User not found or incorrect password'));
return;
} else {
$this->Session->Write( 'user.Username', $user['User']['Username'] );
$this->Session->Write( 'user.Enabled', $user['User']['Enabled'] );
}
}
if ( isset($_REQUEST['auth']) ) {
$user = getAuthUser($_REQUEST['auth']);
} else if ( $mAuth ) {
$user = getAuthUser($mAuth);
if ( !$user ) {
throw new UnauthorizedException(__('User not found'));
throw new UnauthorizedException(__('Invalid Auth Key'));
return;
} else {
if ( ! $this->Session->Write('user.Username', $user['Username']) )
$this->log("Error writing session var user.Username");
if ( ! $this->Session->Write('user.Enabled', $user['Enabled']) )
$this->log("Error writing session var user.Enabled");
}
} # end if REQUEST['auth']
if ( ! $this->Session->read('user.Username') ) {
}
// We need to reject methods that are not authenticated
// besides login and logout
if ( strcasecmp($this->params->action, 'logout') ) {
if ( !( $user and $user['Username'] ) ) {
throw new UnauthorizedException(__('Not Authenticated'));
return;
} else if ( ! $this->Session->read('user.Enabled') ) {
} else if ( !( $user and $user['Enabled'] ) ) {
throw new UnauthorizedException(__('User is not enabled'));
return;
}
$options = array ('conditions' => array ('User.Username' => $this->Session->Read('user.Username')));
$userMonitors = $this->User->find('first', $options);
$this->Session->Write('allowedMonitors',$userMonitors['User']['MonitorIds']);
$this->Session->Write('streamPermission',$userMonitors['User']['Stream']);
$this->Session->Write('eventPermission',$userMonitors['User']['Events']);
$this->Session->Write('controlPermission',$userMonitors['User']['Control']);
$this->Session->Write('systemPermission',$userMonitors['User']['System']);
$this->Session->Write('monitorPermission',$userMonitors['User']['Monitors']);
} else {
// if auth is not on, you can do everything
//$userMonitors = $this->User->find('first', $options);
$this->Session->Write('allowedMonitors','');
$this->Session->Write('streamPermission','View');
$this->Session->Write('eventPermission','Edit');
$this->Session->Write('controlPermission','Edit');
$this->Session->Write('systemPermission','Edit');
$this->Session->Write('monitorPermission','Edit');
}
} # end if ! login or logout
} # end if ZM_OPT_AUTH
} # end function beforeFilter()
}

View File

@ -1,5 +1,6 @@
<?php
App::uses('AppController', 'Controller');
/**
* Events Controller
*
@ -16,8 +17,9 @@ class EventsController extends AppController {
public function beforeFilter() {
parent::beforeFilter();
$canView = $this->Session->Read('eventPermission');
if ($canView =='None') {
global $user;
$canView = (!$user) || ($user['Events'] != 'None');
if ( !$canView ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
@ -32,9 +34,10 @@ class EventsController extends AppController {
public function index() {
$this->Event->recursive = -1;
$allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY);
global $user;
$allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null;
if (!empty($allowedMonitors)) {
if ( $allowedMonitors ) {
$mon_options = array('Event.MonitorId' => $allowedMonitors);
} else {
$mon_options = '';
@ -91,7 +94,7 @@ class EventsController extends AppController {
}
$this->set(compact('events'));
}
} // end public function index()
/**
* view method
@ -108,9 +111,10 @@ class EventsController extends AppController {
throw new NotFoundException(__('Invalid event'));
}
$allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY);
global $user;
$allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null;
if (!empty($allowedMonitors)) {
if ( $allowedMonitors ) {
$mon_options = array('Event.MonitorId' => $allowedMonitors);
} else {
$mon_options = '';
@ -149,7 +153,9 @@ class EventsController extends AppController {
*/
public function add() {
if ($this->Session->Read('eventPermission') != 'Edit') {
global $user;
$canEdit = (!$user) || ($user['Events'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -173,7 +179,9 @@ class EventsController extends AppController {
*/
public function edit($id = null) {
if ($this->Session->Read('eventPermission') != 'Edit') {
global $user;
$canEdit = (!$user) || ($user['Events'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -204,7 +212,9 @@ class EventsController extends AppController {
* @return void
*/
public function delete($id = null) {
if ($this->Session->Read('eventPermission') != 'Edit') {
global $user;
$canEdit = (!$user) || ($user['Events'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -228,7 +238,7 @@ class EventsController extends AppController {
foreach ($this->params['named'] as $param_name => $value) {
// Transform params into mysql
if (preg_match("/interval/i", $value, $matches)) {
if ( preg_match('/interval/i', $value, $matches) ) {
$condition = array("$param_name >= (date_sub(now(), $value))");
} else {
$condition = array($param_name => $value);
@ -254,12 +264,12 @@ class EventsController extends AppController {
$this->Event->recursive = -1;
$results = array();
$moreconditions ="";
$moreconditions = '';
foreach ($this->request->params['named'] as $name => $param) {
$moreconditions = $moreconditions . " AND ".$name.$param;
$moreconditions = $moreconditions . ' AND '.$name.$param;
}
$query = $this->Event->query("select MonitorId, COUNT(*) AS Count from Events WHERE (StartTime >= (DATE_SUB(NOW(), interval $interval)) $moreconditions) GROUP BY MonitorId;");
$query = $this->Event->query("SELECT MonitorId, COUNT(*) AS Count FROM Events WHERE (StartTime >= (DATE_SUB(NOW(), interval $interval)) $moreconditions) GROUP BY MonitorId;");
foreach ($query as $result) {
$results[$result['Events']['MonitorId']] = $result[0]['Count'];
@ -285,13 +295,13 @@ class EventsController extends AppController {
// Find the max Frame for this Event. Error out otherwise.
$this->loadModel('Frame');
if (! $frame = $this->Frame->find('first', array(
if ( !( $frame = $this->Frame->find('first', array(
'conditions' => array(
'EventId' => $event['Event']['Id'],
'Score' => $event['Event']['MaxScore']
)
))) {
throw new NotFoundException(__("Can not find Frame for Event " . $event['Event']['Id']));
))) ) {
throw new NotFoundException(__('Can not find Frame for Event ' . $event['Event']['Id']));
}
$this->loadModel('Config');
@ -304,7 +314,8 @@ class EventsController extends AppController {
$config = $this->Config->find('list', array(
'conditions' => array('OR' => array(
'Name' => array('ZM_WEB_LIST_THUMB_WIDTH',
'Name' => array(
'ZM_WEB_LIST_THUMB_WIDTH',
'ZM_WEB_LIST_THUMB_HEIGHT',
'ZM_EVENT_IMAGE_DIGITS',
'ZM_DIR_IMAGES',
@ -335,7 +346,7 @@ class EventsController extends AppController {
$thumbData['Width'] = (int)$thumbWidth;
$thumbData['Height'] = (int)$thumbHeight;
return( $thumbData );
return $thumbData;
}
public function archive($id = null) {
@ -382,7 +393,7 @@ class EventsController extends AppController {
'Score' => $event['Event']['MaxScore']
)
))) {
throw new NotFoundException(__("Can not find Frame for Event " . $event['Event']['Id']));
throw new NotFoundException(__('Can not find Frame for Event ' . $event['Event']['Id']));
}
return $frame['Frame']['Id'];
}

View File

@ -3,7 +3,7 @@ App::uses('AppController', 'Controller');
class HostController extends AppController {
public $components = array('RequestHandler');
public $components = array('RequestHandler', 'Session');
public function daemonCheck($daemon=false, $args=false) {
$string = Configure::read('ZM_PATH_BIN').'/zmdc.pl check';
@ -30,38 +30,69 @@ class HostController extends AppController {
));
}
function getCredentials() {
// ignore debug warnings from other functions
$this->view='Json';
$credentials = "";
$appendPassword = 0;
function login() {
$cred = $this->_getCredentials();
$ver = $this->_getVersion();
$this->set(array(
'credentials' => $cred[0],
'append_password'=>$cred[1],
'version' => $ver[0],
'apiversion' => $ver[1],
'_serialize' => array('credentials',
'append_password',
'version',
'apiversion'
)));
} // end function login()
// clears out session
function logout() {
global $user;
$this->Session->Write('user', null);
$this->set(array(
'result' => 'ok',
'_serialize' => array('result')
));
} // end function logout()
private function _getCredentials() {
$credentials = '';
$appendPassword = 0;
$this->loadModel('Config');
$isZmAuth = $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_AUTH')))['Config']['Value'];
if ( $isZmAuth ) {
require_once "../../../includes/auth.php"; # in the event we directly call getCredentials.json
$this->Session->read('user'); # this is needed for command line/curl to recognize a session
$zmAuthRelay = $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_RELAY')))['Config']['Value'];
if ( $zmAuthRelay == 'hashed' ) {
$zmAuthHashIps= $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_HASH_IPS')))['Config']['Value'];
$credentials = 'auth='.generateAuthHash($zmAuthHashIps);
}
elseif ($zmAuthRelay == 'plain') {
} else if ( $zmAuthRelay == 'plain' ) {
// user will need to append the store password here
$credentials = "user=".$this->Session->read('user.Username')."&pass=";
$credentials = 'user='.$this->Session->read('user.Username').'&pass=';
$appendPassword = 1;
}
elseif ($zmAuthRelay == 'none') {
$credentials = "user=".$this->Session->read('user.Username');
} else if ( $zmAuthRelay == 'none' ) {
$credentials = 'user='.$this->Session->read('user.Username');
}
}
return array($credentials, $appendPassword);
} // end function _getCredentials
function getCredentials() {
// ignore debug warnings from other functions
$this->view='Json';
$val = $this->_getCredentials();
$this->set(array(
'credentials'=> $credentials,
'append_password'=>$appendPassword,
'credentials'=> $val[0],
'append_password'=>$val[1],
'_serialize' => array('credentials', 'append_password')
) );
}
// If $mid is set, only return disk usage for that monitor
// Else, return an array of total disk usage, and per-monitor
// usage.
@ -139,17 +170,17 @@ class HostController extends AppController {
));
}
function getVersion() {
//throw new UnauthorizedException(__('API Disabled'));
private function _getVersion() {
$version = Configure::read('ZM_VERSION');
// not going to use the ZM_API_VERSION
// requires recompilation and dependency on ZM upgrade
//$apiversion = Configure::read('ZM_API_VERSION');
$apiversion = '1.0';
return array($version, $apiversion);
}
function getVersion() {
$val = $this->_getVersion();
$this->set(array(
'version' => $version,
'apiversion' => $apiversion,
'version' => $val[0],
'apiversion' => $val[1],
'_serialize' => array('version', 'apiversion')
));
}

View File

@ -18,10 +18,13 @@ class MonitorsController extends AppController {
public function beforeRender() {
$this->set($this->Monitor->enumValues());
}
public function beforeFilter() {
parent::beforeFilter();
$canView = $this->Session->Read('monitorPermission');
if ($canView == 'None') {
global $user;
# We already tested for auth in appController, so we just need to test for specific permission
$canView = (!$user) || ($user['Monitors'] != 'None');
if ( !$canView ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
@ -43,8 +46,9 @@ class MonitorsController extends AppController {
$conditions = array();
}
$allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY);
if (!empty($allowedMonitors)) {
global $user;
$allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null;
if ( $allowedMonitors ) {
$conditions['Monitor.Id' ] = $allowedMonitors;
}
$find_array = array('conditions'=>$conditions,'contain'=>array('Group'));
@ -87,8 +91,9 @@ class MonitorsController extends AppController {
if ( !$this->Monitor->exists($id) ) {
throw new NotFoundException(__('Invalid monitor'));
}
$allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY);
if (!empty($allowedMonitors)) {
global $user;
$allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null;
if ( $allowedMonitors ) {
$restricted = array('Monitor.' . $this->Monitor->primaryKey => $allowedMonitors);
} else {
$restricted = '';
@ -114,7 +119,9 @@ class MonitorsController extends AppController {
public function add() {
if ( $this->request->is('post') ) {
if ( $this->Session->Read('systemPermission') != 'Edit' ) {
global $user;
$canAdd = (!$user) || ($user['System'] == 'Edit' );
if ( !$canAdd ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -147,7 +154,9 @@ class MonitorsController extends AppController {
if ( !$this->Monitor->exists($id) ) {
throw new NotFoundException(__('Invalid monitor'));
}
if ($this->Session->Read('monitorPermission') != 'Edit') {
global $user;
$canEdit = (!$user) || ($user['Monitors'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -164,7 +173,15 @@ class MonitorsController extends AppController {
$func = $Monitor['Function'];
// We don't pass the request data as the monitor object because it may be a subset of the full monitor array
$this->daemonControl($this->Monitor->id, 'stop');
if ( ( $func != 'None' ) and ( (!defined('ZM_SERVER_ID')) or ($Monitor['ServerId']==ZM_SERVER_ID) ) ) {
if (
( $func != 'None' )
and
(
(!defined('ZM_SERVER_ID'))
or
($Monitor['ServerId']==ZM_SERVER_ID)
)
) {
$this->daemonControl($this->Monitor->id, 'start');
}
} else {
@ -206,7 +223,7 @@ class MonitorsController extends AppController {
}
public function sourceTypes() {
$sourceTypes = $this->Monitor->query("describe Monitors Type;");
$sourceTypes = $this->Monitor->query('describe Monitors Type;');
preg_match('/^enum\((.*)\)$/', $sourceTypes[0]['COLUMNS']['Type'], $matches);
foreach( explode(',', $matches[1]) as $value ) {
@ -255,7 +272,6 @@ class MonitorsController extends AppController {
$config = $this->Config->find('first', $options);
$zmOptAuth = $config['Config']['Value'];
$options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_RELAY'));
$config = $this->Config->find('first', $options);
$zmAuthRelay = $config['Config']['Value'];
@ -363,5 +379,4 @@ class MonitorsController extends AppController {
$status = exec( $shellcmd );
}
}
} // end class MonitorsController

View File

@ -8,7 +8,6 @@ App::uses('AppController', 'Controller');
*/
class ServersController extends AppController {
/**
* Components
*
@ -16,18 +15,16 @@ class ServersController extends AppController {
*/
public $components = array('Paginator', 'RequestHandler');
public function beforeFilter() {
parent::beforeFilter();
$canView = $this->Session->Read('streamPermission');
if ($canView =='None') {
global $user;
$canView = (!$user) || ($user['System'] != 'None');
if ( !$canView ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
}
/**
* index method
*
@ -78,8 +75,9 @@ public function beforeFilter() {
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;
}
@ -103,14 +101,16 @@ public function beforeFilter() {
public function edit($id = null) {
$this->Server->id = $id;
if (!$this->Server->exists($id)) {
throw new NotFoundException(__('Invalid server'));
}
if ($this->Session->Read('systemPermission') != 'Edit')
{
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
if ( !$this->Server->exists($id) ) {
throw new NotFoundException(__('Invalid server'));
}
if ( $this->Server->save($this->request->data) ) {
$message = 'Saved';
} else {
@ -133,15 +133,17 @@ public function beforeFilter() {
* @return void
*/
public function delete($id = null) {
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
$this->Server->id = $id;
if ( !$this->Server->exists() ) {
throw new NotFoundException(__('Invalid server'));
}
if ($this->Session->Read('systemPermission') != 'Edit')
{
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
$this->request->allowMethod('post', 'delete');
#$this->daemonControl($this->Server->id, 'stop');

View File

@ -13,16 +13,14 @@ public $components = array('RequestHandler');
public function beforeFilter() {
parent::beforeFilter();
$canView = $this->Session->Read('systemPermission');
if ($canView =='None')
{
global $user;
$canView = (!$user) || ($user['System'] != 'None');
if ( !$canView ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
}
/**
* index method
*
@ -86,8 +84,9 @@ public function beforeFilter() {
throw new NotFoundException(__('Invalid state'));
}
if ($this->Session->Read('systemPermission') != 'Edit')
{
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -111,8 +110,9 @@ public function beforeFilter() {
*/
public function delete($id = null) {
$this->State->id = $id;
if ($this->Session->Read('systemPermission') != 'Edit')
{
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -129,8 +129,9 @@ public function beforeFilter() {
}
public function change() {
if ($this->Session->Read('systemPermission') != 'Edit')
{
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}

View File

@ -0,0 +1,172 @@
<?php
App::uses('AppController', 'Controller');
/**
* Users Controller
*
* @property User $User
*/
class UsersController extends AppController {
/**
* Components
*
* @var array
*/
public $components = array('RequestHandler', 'Paginator');
/**
* index method
*
* @return void
* This also creates a thumbnail for each user.
*/
public function index() {
$this->User->recursive = 0;
$users = $this->Paginator->paginate('User');
$this->set(compact('users'));
}
/**
* view method
*
* @throws NotFoundException
* @param string $id
* @return void
*/
public function view($id = null) {
$this->loadModel('Config');
$configs = $this->Config->find('list', array(
'fields' => array('Name', 'Value'),
'conditions' => array('Name' => array('ZM_DIR_EVENTS'))
));
$this->User->recursive = 1;
if (!$this->User->exists($id)) {
throw new NotFoundException(__('Invalid user'));
}
$options = array('conditions' => array('User.' . $this->User->primaryKey => $id));
$user = $this->User->find('first', $options);
$this->set(array(
'user' => $user,
'_serialize' => array('user')
));
}
/**
* add method
*
* @return void
*/
public function add() {
if ($this->request->is('post')) {
$this->User->create();
if ($this->User->save($this->request->data)) {
return $this->flash(__('The user has been saved.'), array('action' => 'index'));
}
$this->Session->setFlash(
__('The user could not be saved. Please, try again.')
);
}
}
/**
* edit method
*
* @throws NotFoundException
* @param string $id
* @return void
*/
public function edit($id = null) {
$this->User->id = $id;
if (!$this->User->exists($id)) {
throw new NotFoundException(__('Invalid user'));
}
if ($this->request->is('post') || $this->request->is('put')) {
if ($this->User->save($this->request->data)) {
$message = 'Saved';
} else {
$message = 'Error';
}
} else {
$this->request->data = $this->User->read(null, $id);
unset($this->request->data['User']['password']);
}
$this->set(array(
'message' => $message,
'_serialize' => array('message')
));
}
/**
* delete method
*
* @throws NotFoundException
* @param string $id
* @return void
*/
public function delete($id = null) {
$this->User->id = $id;
if (!$this->User->exists()) {
throw new NotFoundException(__('Invalid user'));
}
$this->request->allowMethod('post', 'delete');
if ($this->User->delete()) {
$message = 'The user has been deleted.';
} else {
$message = 'The user could not be deleted. Please, try again.';
}
$this->set(array(
'message' => $message,
'_serialize' => array('message')
));
}
public function beforeFilter() {
parent::beforeFilter();
$this->loadModel('Config');
$configs = $this->Config->find('list', array(
'fields' => array('Name', 'Value'),
'conditions' => array('Name' => array('ZM_OPT_USE_AUTH'))
));
if ( $configs['ZM_OPT_USE_AUTH'] ) {
$this->Auth->allow('add','logout');
} else {
$this->Auth->allow();
}
}
public function login() {
$this->loadModel('Config');
$configs = $this->Config->find('list', array(
'fields' => array('Name', 'Value'),
'conditions' => array('Name' => array('ZM_OPT_USE_AUTH'))
));
if ( ! $configs['ZM_OPT_USE_AUTH'] ) {
$this->set(array(
'message' => 'Login is not required.',
'_serialize' => array('message')
));
return;
}
if ($this->request->is('post')) {
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirectUrl());
}
$this->Session->setFlash(__('Invalid username or password, try again'));
}
}
public function logout() {
return $this->redirect($this->Auth->logout());
}
}

View File

@ -96,4 +96,5 @@ class ZonePresetsController extends AppController {
} else {
return $this->flash(__('The zone preset could not be deleted. Please, try again.'), array('action' => 'index'));
}
}}
}
} // end class ZonePresetsController

View File

@ -16,13 +16,13 @@ public $components = array('RequestHandler');
public function beforeFilter() {
parent::beforeFilter();
$canView = $this->Session->Read('monitorPermission');
if ($canView =='None')
{
global $user;
$canView = (!$user) || $user['Monitors'] != 'None';
if ( !$canView ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
}
// Find all zones which belong to a MonitorId
@ -43,13 +43,11 @@ public function forMonitor($id = null) {
public function index() {
$this->Zone->recursive = -1;
$allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY);
if (!empty($allowedMonitors))
{
global $user;
$allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'],NULL, PREG_SPLIT_NO_EMPTY) : null;
if ( $allowedMonitors ) {
$mon_options = array('Zones.MonitorId' => $allowedMonitors);
}
else
{
} else {
$mon_options = '';
}
$zones = $this->Zone->find('all',$mon_options);
@ -58,6 +56,7 @@ public function index() {
'_serialize' => array('zones')
));
}
/**
* add method
*
@ -65,6 +64,14 @@ public function index() {
*/
public function add() {
if ( $this->request->is('post') ) {
global $user;
$canEdit = (!$user) || $user['Monitors'] == 'Edit';
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
$this->Zone->create();
if ( $this->Zone->save($this->request->data) ) {
return $this->flash(__('The zone has been saved.'), array('action' => 'index'));
@ -88,6 +95,12 @@ public function index() {
throw new NotFoundException(__('Invalid zone'));
}
if ( $this->request->is(array('post', 'put')) ) {
global $user;
$canEdit = (!$user) || $user['Monitors'] == 'Edit';
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
if ( $this->Zone->save($this->request->data) ) {
return $this->flash(__('The zone has been saved.'), array('action' => 'index'));
}
@ -112,6 +125,12 @@ public function index() {
throw new NotFoundException(__('Invalid zone'));
}
$this->request->allowMethod('post', 'delete');
global $user;
$canEdit = (!$user) || $user['Monitors'] == 'Edit';
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
if ( $this->Zone->delete() ) {
return $this->flash(__('The zone has been deleted.'), array('action' => 'index'));
} else {
@ -119,8 +138,6 @@ public function index() {
}
}
public function createZoneImage($id = null) {
$this->loadModel('Monitor');
$this->Monitor->id = $id;
@ -128,7 +145,6 @@ public function index() {
throw new NotFoundException(__('Invalid zone'));
}
$this->loadModel('Config');
$zm_dir_images = $this->Config->find('list', array(
'conditions' => array('Name' => 'ZM_DIR_IMAGES'),
@ -149,6 +165,5 @@ public function index() {
'status' => $status,
'_serialize' => array('status')
));
}
}
} // end class

View File

@ -1,12 +1,28 @@
<?php
App::uses('AppModel', 'Model');
/**
* User Model
*
* @property Monitor $Monitor
* @property Frame $Frame
*/
class User extends AppModel {
public $validate = array(
'Username' => array(
'required' => array(
'rule' => array('notEmpty'),
'message' => 'A username is required'
)
),
'Password' => array(
'required' => array(
'rule' => array('notEmpty'),
'message' => 'A password is required'
)
)
);
/**
* Use table
*
@ -26,6 +42,48 @@ class User extends AppModel {
*
* @var string
*/
public $displayField = 'Name';
public $displayField = 'Username';
//The Associations below have been created with all possible keys, those that are not needed can be removed
/**
* belongsTo associations
*
* @var array
*/
public $belongsTo = array(
/*'Monitor' => array(
'className' => 'Monitor',
'foreignKey' => 'MonitorId',
'conditions' => '',
'fields' => '',
'order' => ''
)
*/
);
/**
* hasMany associations
*
* @var array
*/
public $hasMany = array(
/*
'Frame' => array(
'className' => 'Frame',
'foreignKey' => 'UserId',
'dependent' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
*/
);
}

View File

@ -0,0 +1,15 @@
<!-- app/View/Users/add.ctp -->
<div class="users form">
<?php echo $this->Form->create('User'); ?>
<fieldset>
<legend><?php echo __('Add User'); ?></legend>
<?php echo $this->Form->input('username');
echo $this->Form->input('password');
echo $this->Form->input('role', array(
'options' => array('admin' => 'Admin', 'author' => 'Author')
));
?>
</fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
</div>

View File

@ -0,0 +1,5 @@
<?php
$array['users'] = $users;
$array['pagination'] = $this->Paginator->params();
echo json_encode($array);
?>

View File

@ -0,0 +1 @@
echo json_encode($user);

View File

@ -0,0 +1,13 @@
<div class="users form">
<?php echo $this->Session->flash('auth'); ?>
<?php echo $this->Form->create('User'); ?>
<fieldset>
<legend>
<?php echo __('Please enter your username and password'); ?>
</legend>
<?php echo $this->Form->input('username');
echo $this->Form->input('password');
?>
</fieldset>
<?php echo $this->Form->end(__('Login')); ?>
</div>

View File

@ -0,0 +1,2 @@
$xml = Xml::fromArray(array('response' => $events));
echo $xml->asXML();

View File

@ -0,0 +1,2 @@
$xml = Xml::fromArray(array('response' => $event));
echo $xml->asXML();

View File

@ -12,6 +12,9 @@ private $defaults = array(
'CanMoveRel' => 0,
'CanMoveCon' => 0,
'CanPan' => 0,
'CanReset' => 0,
'CanSleep' => 0,
'CanWake' => 0,
'MinPanRange' => NULL,
'MaxPanRange' => NULL,
'MinPanStep' => NULL,
@ -103,7 +106,7 @@ private $defaults = array(
if ( $IdOrRow ) {
$row = NULL;
if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) {
$row = dbFetchOne( 'SELECT * FROM Control WHERE Id=?', NULL, array( $IdOrRow ) );
$row = dbFetchOne( 'SELECT * FROM Controls WHERE Id=?', NULL, array( $IdOrRow ) );
if ( ! $row ) {
Error("Unable to load Control record for Id=" . $IdOrRow );
}

View File

@ -9,17 +9,19 @@ private $defaults = array(
'Name' => '',
'StorageId' => 0,
'ServerId' => 0,
'Type' => 'Ffmpeg',
'Function' => 'None',
'Enabled' => 1,
'LinkedMonitors' => null,
'Width' => null,
'Height' => null,
'Orientation' => null,
'AnalysisFPSLimit' => null,
'ZoneCount' => 0,
'Triggers' => null,
'Type' => 'Ffmpeg',
'MaxFPS' => null,
'AlarmMaxFPS' => null,
'Refresh' => null,
);
private $status_fields = array(
'AnalysisFPS' => null,

View File

@ -141,19 +141,30 @@ class Storage {
}
public function disk_total_space() {
if ( !array_key_exists('disk_total_space', $this) ) {
$this->{'disk_total_space'} = disk_total_space($this->Path());
$path = $this->Path();
if ( file_exists($path) ) {
$this->{'disk_total_space'} = disk_total_space($path);
} else {
Error("Path $path does not exist.");
$this->{'disk_total_space'} = 0;
}
}
return $this->{'disk_total_space'};
}
public function disk_used_space() {
# 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 ( ( !array_key_exists('disk_used_space', $this)) or !$this->{'disk_used_space'} ) {
if ( $this->{'Type'} == 's3fs' ) {
$this->{'disk_used_space'} = $this->disk_event_space();
} else {
$path = $this->Path();
if ( file_exists($path) ) {
$this->{'disk_used_space'} = disk_total_space($path) - disk_free_space($path);
} else {
Error("Path $path does not exist.");
$this->{'disk_used_space'} = 0;
}
}
}
return $this->{'disk_used_space'};

View File

@ -118,6 +118,7 @@ if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 're
userLogout();
$view='login';
$refreshParent = true;
return;
} else {
//Let them login but show an error
echo '<script type="text/javascript">alert("'.translate('RecaptchaWarning').'"); </script>';
@ -125,7 +126,6 @@ if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 're
}
}
} // end if success==false
} // end if using reCaptcha
$username = validStr($_REQUEST['username']);

View File

@ -19,7 +19,7 @@
//
function userLogin($username, $password='', $passwordHashed=false) {
global $user, $cookies;
global $user;
$sql = 'SELECT * FROM Users WHERE Enabled=1';
$sql_values = NULL;
@ -34,7 +34,12 @@ function userLogin($username, $password='', $passwordHashed=false) {
$sql .= ' AND Username=?';
$sql_values = array($username);
}
$close_session = 0;
if ( !is_session_started() ) {
Logger::Debug("Starting session in userLogin");
session_start();
$close_session = 1;
}
$_SESSION['username'] = $username;
if ( ZM_AUTH_RELAY == 'plain' ) {
// Need to save this in session
@ -54,7 +59,9 @@ function userLogin($username, $password='', $passwordHashed=false) {
$_SESSION['loginFailed'] = true;
unset($user);
}
if ( $close_session )
session_write_close();
return isset($user) ? $user: null;
} # end function userLogin
function userLogout() {
@ -121,7 +128,11 @@ function generateAuthHash($useRemoteAddr, $force=false) {
#Logger::Debug("Generated using hour:".$local_time[2] . ' mday:' . $local_time[3] . ' month:'.$local_time[4] . ' year: ' . $local_time[5] );
$auth = md5($authKey);
if ( !$force ) {
$close_session = 0;
if ( !is_session_started() ) {
session_start();
$close_session = 1;
}
$_SESSION['AuthHash'] = $auth;
$_SESSION['AuthHashGeneratedAt'] = $time;
session_write_close();
@ -155,4 +166,17 @@ function canEdit($area, $mid=false) {
return ( $user[$area] == 'Edit' && ( !$mid || visibleMonitor($mid) ));
}
function is_session_started() {
if ( php_sapi_name() !== 'cli' ) {
if ( version_compare(phpversion(), '5.4.0', '>=') ) {
return session_status() === PHP_SESSION_ACTIVE ? TRUE : FALSE;
} else {
return session_id() === '' ? FALSE : TRUE;
}
} else {
Warning("php_sapi_name === 'cli'");
}
return FALSE;
}
?>

View File

@ -212,7 +212,7 @@ function process_configfile($configFile) {
continue;
elseif ( preg_match( '/^\s*#/', $str ))
continue;
elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*(.*?)\s*$/', $str, $matches ))
elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/', $str, $matches ))
$configvals[$matches[1]] = $matches[2];
}
fclose( $cfg );

View File

@ -436,16 +436,34 @@ function htmlSelect( $name, $contents, $values, $behaviours=false ) {
}
function htmlOptions($contents, $values) {
$html = '';
foreach ( $contents as $value=>$text ) {
if ( is_array( $text ) )
$text = $text['Name'];
else if ( is_object( $text ) )
$text = $text->Name();
$selected = is_array( $values ) ? in_array( $value, $values ) : !strcmp($value, $values);
$html .= "<option value=\"$value\"".($selected?" selected=\"selected\"":'').">$text</option>";
$options_html = '';
foreach ( $contents as $value=>$option ) {
$disabled = 0;
$text = '';
if ( is_array($option) ) {
if ( isset($option['Name']) )
$text = $option['Name'];
else if ( isset($option['text']) )
$text = $option['text'];
if ( isset($option['disabled']) ) {
$disabled = $option['disabled'];
Error("Setting to disabled");
}
return $html;
} else if ( is_object($option) ) {
$text = $option->Name();
} else {
$text = $option;
}
$selected = is_array($values) ? in_array($value, $values) : !strcmp($value, $values);
$options_html .= "<option value=\"$value\"".
($selected?' selected="selected"':'').
($disabled?' disabled="disabled"':'').
">$text</option>";
}
return $options_html;
}
function truncText( $text, $length, $deslash=1 ) {
@ -2064,7 +2082,7 @@ function cache_bust( $file ) {
$dirname = preg_replace( '/\//', '_', $parts['dirname'] );
$cacheFile = $dirname.'_'.$parts['filename'].'-'.$css.'-'.filemtime($file).'.'.$parts['extension'];
if ( file_exists(ZM_DIR_CACHE.'/'.$cacheFile) or symlink(ZM_PATH_WEB.'/'.$file, ZM_DIR_CACHE.'/'.$cacheFile) ) {
return '/zm/cache/'.$cacheFile;
return 'cache/'.$cacheFile;
} else {
Warning("Failed linking $file to $cacheFile");
}

View File

@ -51,7 +51,12 @@ require_once( 'includes/Event.php' );
require_once( 'includes/Group.php' );
require_once( 'includes/Monitor.php' );
if ( isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ) {
if (
(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on')
or
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) and ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'))
) {
$protocol = 'https';
} else {
$protocol = 'http';

View File

@ -663,6 +663,7 @@ $SLANG = array(
'ShowFilterWindow' => 'Show Filter Window',
'ShowTimeline' => 'Show Timeline',
'SignalCheckColour' => 'Signal Check Colour',
'SignalCheckPoints' => 'Signal Check Points',
'Size' => 'Size',
'SkinDescription' => 'Change the skin for this session',
'CSSDescription' => 'Change the css for this session',
@ -697,6 +698,7 @@ $SLANG = array(
'Stills' => 'Stills',
'Stopped' => 'Stopped',
'Stop' => 'Stop',
'StorageArea' => 'Storage Area',
'StorageScheme' => 'Scheme',
'StreamReplayBuffer' => 'Stream Replay Image Buffer',
'Stream' => 'Stream',

View File

@ -41,7 +41,7 @@ var popupSizes = {
'filter': { 'width': 900, 'height': 700 },
'frame': { 'addWidth': 32, 'minWidth': 384, 'addHeight': 200 },
'frames': { 'width': 600, 'height': 600 },
'function': { 'width': 350, 'height': 160 },
'function': { 'width': 350, 'height': 260 },
'group': { 'width': 760, 'height': 600 },
'groups': { 'width': 540, 'height': 420 },
'image': { 'addWidth': 48, 'addHeight': 80 },
@ -54,7 +54,7 @@ var popupSizes = {
'monitorselect':{ 'width': 160, 'height': 200 },
'montage': { 'width': -1, 'height': -1 },
'onvifprobe': { 'width': 700, 'height': 550 },
'optionhelp': { 'width': 400, 'height': 320 },
'optionhelp': { 'width': 400, 'height': 400 },
'options': { 'width': 1000, 'height': 660 },
'preset': { 'width': 300, 'height': 220 },
'server': { 'width': 600, 'height': 405 },

View File

@ -1,42 +0,0 @@
{
"name": "chosen",
"description": "Chosen is a JavaScript plugin that makes select boxes user-friendly. It is currently available in both jQuery and Prototype flavors.",
"keywords": [
"select",
"multiselect",
"dropdown",
"form",
"input",
"ui"
],
"homepage": "https://harvesthq.github.io/chosen/",
"license": "https://github.com/harvesthq/chosen/blob/master/LICENSE.md",
"authors": [
{
"name": "Patrick Filler",
"url": "https://github.com/pfiller"
},
{
"name": "Christophe Coevoet",
"url": "https://github.com/stof"
},
{
"name": "Ken Earley",
"url": "https://github.com/kenearley"
},
{
"name": "Koen Punt",
"url": "https://github.com/koenpunt"
}
],
"dependencies": {},
"main": [
"chosen.jquery.js",
"chosen.css"
],
"ignore": [],
"repository": {
"type": "git",
"url": "https://github.com/harvesthq/chosen.git"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,11 +0,0 @@
var config = {
'.chosen-select' : {},
'.chosen-select-deselect' : { allow_single_deselect: true },
'.chosen-select-no-single' : { disable_search_threshold: 10 },
'.chosen-select-no-results': { no_results_text: 'Oops, nothing found!' },
'.chosen-select-rtl' : { rtl: true },
'.chosen-select-width' : { width: '95%' }
}
for (var selector in config) {
$(selector).chosen(config[selector]);
}

View File

@ -1,16 +0,0 @@
document.observe('dom:loaded', function(evt) {
var config = {
'.chosen-select' : {},
'.chosen-select-deselect' : { allow_single_deselect: true },
'.chosen-select-no-single' : { disable_search_threshold: 10 },
'.chosen-select-no-results': { no_results_text: 'Oops, nothing found!' },
'.chosen-select-rtl' : { rtl: true },
'.chosen-select-width' : { width: '95%' }
}
for (var selector in config) {
$$(selector).each(function(element) {
new Chosen(element, config[selector]);
});
}
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -1,108 +0,0 @@
/**
* okaidia theme for JavaScript, CSS and HTML
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
* @author ocodia
*/
code[class*="language-"],
pre[class*="language-"] {
color: #f8f8f2;
text-shadow: 0 1px rgba(0,0,0,0.3);
font-family: Consolas, Monaco, 'Andale Mono', monospace;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #272822;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #f8f8f2;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag {
color: #f92672;
}
.token.boolean,
.token.number{
color: #ae81ff;
}
.token.selector,
.token.attr-name,
.token.string {
color: #a6e22e;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #f8f8f2;
}
.token.atrule,
.token.attr-value
{
color: #e6db74;
}
.token.keyword{
color: #66d9ef;
}
.token.regex,
.token.important {
color: #fd971f;
}
.token.important {
font-weight: bold;
}
.token.entity {
cursor: help;
}

View File

@ -1,9 +0,0 @@
/**
* Prism: Lightweight, robust, elegant syntax highlighting
* MIT license http://www.opensource.org/licenses/mit-license.php/
* @author Lea Verou http://lea.verou.me
*/(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ");var l={element:r,language:o,grammar:u,code:f};t.hooks.run("before-highlight",l);if(i&&self.Worker){var c=new Worker(t.filename);c.onmessage=function(e){l.highlightedCode=n.stringify(JSON.parse(e.data),o);t.hooks.run("before-insert",l);l.element.innerHTML=l.highlightedCode;s&&s.call(l.element);t.hooks.run("after-highlight",l)};c.postMessage(JSON.stringify({language:l.language,code:l.code}))}else{l.highlightedCode=t.highlight(l.code,l.grammar,l.language);t.hooks.run("before-insert",l);l.element.innerHTML=l.highlightedCode;s&&s.call(r);t.hooks.run("after-highlight",l)}},highlight:function(e,r,i){return n.stringify(t.tokenize(e,r),i)},tokenize:function(e,n,r){var i=t.Token,s=[e],o=n.rest;if(o){for(var u in o)n[u]=o[u];delete n.rest}e:for(var u in n){if(!n.hasOwnProperty(u)||!n[u])continue;var a=n[u],f=a.inside,l=!!a.lookbehind,c=0;a=a.pattern||a;for(var h=0;h<s.length;h++){var p=s[h];if(s.length>e.length)break e;if(p instanceof i)continue;a.lastIndex=0;var d=a.exec(p);if(d){l&&(c=d[1].length);var v=d.index-1+c,d=d[0].slice(c),m=d.length,g=v+m,y=p.slice(0,v+1),b=p.slice(g+1),w=[h,1];y&&w.push(y);var E=new i(u,f?t.tokenize(d,f):d);w.push(E);b&&w.push(b);Array.prototype.splice.apply(s,w)}}}return s},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e,r,i){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]")return e.map(function(t){return n.stringify(t,r,e)}).join("");var s={type:e.type,content:n.stringify(e.content,r,i),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:i};s.type=="comment"&&(s.attributes.spellcheck="true");t.hooks.run("wrap",s);var o="";for(var u in s.attributes)o+=u+'="'+(s.attributes[u]||"")+'"';return"<"+s.tag+' class="'+s.classes.join(" ")+'" '+o+">"+s.content+"</"+s.tag+">"};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();;
Prism.languages.markup={comment:/&lt;!--[\w\W]*?-->/g,prolog:/&lt;\?.+?\?>/,doctype:/&lt;!DOCTYPE.+?>/,cdata:/&lt;!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/&lt;\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|\w+))?\s*)*\/?>/gi,inside:{tag:{pattern:/^&lt;\/?[\w:-]+/i,inside:{punctuation:/^&lt;\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,inside:{punctuation:/=|>|"/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&amp;#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&amp;/,"&"))});;
Prism.languages.css={comment:/\/\*[\w\W]*?\*\//g,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*{))/gi,inside:{punctuation:/[;:]/g}},url:/url\((["']?).*?\1\)/gi,selector:/[^\{\}\s][^\{\};]*(?=\s*\{)/g,property:/(\b|\B)[\w-]+(?=\s*:)/ig,string:/("|')(\\?.)*?\1/g,important:/\B!important\b/gi,ignore:/&(lt|gt|amp);/gi,punctuation:/[\{\};:]/g};Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{style:{pattern:/(&lt;|<)style[\w\W]*?(>|&gt;)[\w\W]*?(&lt;|<)\/style(>|&gt;)/ig,inside:{tag:{pattern:/(&lt;|<)style[\w\W]*?(>|&gt;)|(&lt;|<)\/style(>|&gt;)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.css}}});;
Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/ig,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/ig,inside:{punctuation:/\(/}}, number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|&lt;=?|>=?|={1,3}|(&amp;){1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};;
Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|new|with|typeof|try|catch|finally|null|break|continue)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(&lt;|<)script[\w\W]*?(>|&gt;)[\w\W]*?(&lt;|<)\/script(>|&gt;)/ig,inside:{tag:{pattern:/(&lt;|<)script[\w\W]*?(>|&gt;)|(&lt;|<)\/script(>|&gt;)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});;

View File

@ -1,3 +0,0 @@
Outdated, source-less minified file. Original may have been lost.
https://github.com/harvesthq/chosen/issues/3005

View File

@ -1,219 +0,0 @@
/* Reset */
html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; }
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; }
blockquote, q { quotes: none; }
blockquote:before, blockquote:after, q:before, q:after { content: ""; content: none; }
ins { background-color: #ff9; color: #000; text-decoration: none; }
mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; }
del { text-decoration: line-through; }
abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; }
table { border-collapse: collapse; border-spacing: 0; }
hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
input, select { vertical-align: middle; }
body { font:13px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */
select, input, textarea, button { font:99% sans-serif; }
pre, code, kbd, samp { font-family: monospace, sans-serif; }
body { background: #EEE; color: #444; line-height: 1.4em; }
header h1 { color: black; font-size: 2em; line-height: 1.1em; display: inline-block; height: 27px; margin: 20px 0 25px; }
header h1 small { font-size: 0.6em; }
div#content { background: white; border: 1px solid #ccc; border-width: 0 1px 1px; margin: 0 auto; padding: 40px 50px 40px; width: 738px; }
footer { color: #999; padding-top: 40px; font-size: 0.8em; text-align: center; }
body { font-family: sans-serif; font-size: 1em; }
p { margin: 0 0 .7em; max-width: 700px; }
table+p { margin-top: 1em; }
h2 { border-bottom: 1px solid #ccc; font-size: 1.2em; margin: 3em 0 1em 0; font-weight: bold;}
h3 { font-weight: bold; }
h2.intro { border-bottom: none; font-size: 1em; font-weight: normal; margin-top:0; }
ul li { list-style: disc; margin-left: 1em; margin-bottom: 1.25em; }
ol li { margin-left: 1.25em; }
ol ul, ul ul { margin: .25em 0 0; }
ol ul li, ul ul li { list-style-type: circle; margin: 0 0 .25em 1em; }
li > p { margin-top: .25em; }
div.side-by-side { width: 100%; margin-bottom: 1em; }
div.side-by-side > div { float: left; width: 49%; }
div.side-by-side > div > em { margin-bottom: 10px; display: block; }
.faqs em { display: block; }
.clearfix:after {
content: "\0020";
display: block;
height: 0;
clear: both;
overflow: hidden;
visibility: hidden;
}
a { color: #F36C00; outline: none; text-decoration: none; }
a:hover { text-decoration: underline; }
ul.credits li { margin-bottom: .25em; }
strong { font-weight: bold; }
i { font-style: italic; }
.button {
background: #fafafa;
background: -webkit-linear-gradient(top, #ffffff, #eeeeee);
background: -moz-linear-gradient(top, #ffffff, #eeeeee);
background: -o-linear-gradient(top, #ffffff, #eeeeee);
background: linear-gradient(to bottom, #ffffff, #eeeeee);
border: 1px solid #bbbbbb;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.2);
color: #555555;
cursor: pointer;
display: inline-block;
font-family: "Helvetica Neue", Arial, Verdana, "Nimbus Sans L", sans-serif;
font-size: 13px;
font-weight: 500;
height: 31px;
line-height: 28px;
outline: none;
padding: 0 13px;
text-shadow: 0 1px 0 white;
text-decoration: none;
vertical-align: middle;
white-space: nowrap;
-webkit-font-smoothing: antialiased;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.button-blue {
background: #1385e5;
background: -webkit-linear-gradient(top, #53b2fc, #1385e5);
background: -moz-linear-gradient(top, #53b2fc, #1385e5);
background: -o-linear-gradient(top, #53b2fc, #1385e5);
background: linear-gradient(to bottom, #53b2fc, #1385e5);
border-color: #075fa9;
color: white;
font-weight: bold;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.4);
}
/* Tweak navbar brand link to be super sleek
-------------------------------------------------- */
.oss-bar {
top: 0;
right: 20px;
position: fixed;
z-index: 1030;
}
.oss-bar ul {
float: right;
margin: 0;
list-style: none;
}
.oss-bar ul li {
list-style: none;
float: left;
line-height: 0;
margin: 0;
}
.oss-bar ul li a {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
border: 0;
margin-top: -10px;
display: block;
height: 58px;
background: #F36C00 url(oss-credit.png) no-repeat 20px 22px;
padding: 22px 20px 12px 20px;
text-indent: 120%; /* stupid padding */
white-space: nowrap;
overflow: hidden;
-webkit-transition: all 0.10s ease-in-out;
-moz-transition: all 0.10s ease-in-out;
transition: all 0.15s ease-in-out;
}
.oss-bar ul li a:hover {
margin-top: 0px;
}
.oss-bar a.harvest {
width: 196px;
background-color: #F36C00;
background-position: -142px 22px;
padding-right: 22px; /* optical illusion */
}
.oss-bar a.fork {
width: 162px;
background-color: #333333;
}
.docs-table th, .docs-table td {
border: 1px solid #000;
padding: 4px 6px;
white-space: nowrap;
}
.docs-table td:last-child {
white-space: normal;
}
.docs-table th {
font-weight: bold;
text-align: left;
}
#content pre[class*=language-] {
font-size: 14px;
margin-bottom: 20px;
}
#content pre[class*=language-] code {
font-size: 14px;
padding: 0;
}
#content code[class*=language-] {
font-size: 12px;
padding: 2px 4px;
}
.anchor {
color: inherit;
position: relative;
}
.anchor:hover {
background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSI3Ij48ZyBmaWxsPSIjNDE0MDQyIj48cGF0aCBkPSJNOS44IDdoLS45bC0uOS0uMWMtLjctLjMtMS40LS43LTEuOC0xLjMtLjItLjEtLjMtLjMtLjMtLjVsLS4zLS40Yy0uMS0uNC0uMi0uOC0uMi0xLjIgMC0uNC4xLS44LjItMS4yaDEuN2MtLjMuNC0uNC44LS40IDEuMiAwIC40LjEuOC4zIDEuMS4xLjIuMi4zLjQuNC4xLjEuMi4yLjQuMy4zLjIuNy4zIDEgLjNoMy40YzEuMiAwIDIuMi0uOSAyLjItMi4xcy0xLTIuMS0yLjItMi4xaC0xLjRjLS4zLS42LS43LTEtMS4yLTEuNGgyLjZjMiAwIDMuNiAxLjYgMy42IDMuNXMtMS42IDMuNS0zLjYgMy41aC0yLjZ6TTguNCAyYy0uMS0uMS0uMi0uMy0uNC0uMy0uMy0uMi0uNy0uMy0xLS4zaC0zLjRjLTEuMiAwLTIuMi45LTIuMiAyLjEgMCAxLjIgMSAyLjEgMi4yIDIuMWgxLjRjLjMuNS43IDEgMS4yIDEuNGgtMi42Yy0yIDAtMy42LTEuNi0zLjYtMy41czEuNi0zLjUgMy42LTMuNWgzLjUwMDAwMDAwMDAwMDAwMDRsLjkuMWMuNy4yIDEuNC43IDEuOCAxLjMuMS4xLjIuMy4zLjUuMS4xLjIuMy4yLjUuMS40LjIuOC4yIDEuMiAwIC40LS4xLjgtLjIgMS4yaC0xLjZjLjMtLjUuNC0uOS40LTEuM3MtLjEtLjgtLjMtMS4xYy0uMS0uMi0uMi0uMy0uNC0uNHoiLz48L2c+PC9zdmc+) 0 50% no-repeat;
background-size: 21px 9px;
margin-left: -27px;
padding-left: 27px;
text-decoration: none;
}
.select,
.chosen-select,
.chosen-select-no-single,
.chosen-select-no-results,
.chosen-select-deselect,
.chosen-select-rtl,
.chosen-select-width {
width: 350px;
}
.jquery-version-refer {
margin-top: 40px;
font-style: italic;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,306 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Chosen: A jQuery Plugin by Harvest to Tame Unwieldy Select Boxes</title>
<link rel="stylesheet" href="docsupport/style.css">
<link rel="stylesheet" href="docsupport/prism.css">
<link rel="stylesheet" href="chosen.css">
<style type="text/css" media="all">
/* fix rtl for demo */
.chosen-rtl .chosen-drop { left: -9000px; }
</style>
</head>
<body>
<div id="container">
<div id="content">
<header>
<h1>Chosen <small>(<span id="latest-version">v1.8.2</span>)</small></h1>
</header>
<p>Chosen has a number of options and attributes that allow you to have full control of your select boxes.</p>
<h2><a name="options" class="anchor" href="#options">Options</a></h2>
<p>The following options are available to pass into Chosen on instantiation.</p>
<h3>Example:</h3>
<pre>
<code class="language-javascript">$(".my_select_box").chosen({
disable_search_threshold: 10,
no_results_text: "Oops, nothing found!",
width: "95%"
});</code>
</pre>
<table class="docs-table">
<tr>
<th>Option</th><th>Default</th><th>Description</th>
</tr>
<tr>
<td>allow_single_deselect</td>
<td>false</td>
<td>When set to <code class="language-javascript">true</code> on a single select, Chosen adds a UI element which selects the first element (if it is blank).</td>
</tr>
<tr>
<td>disable_search</td>
<td>false</td>
<td>When set to <code class="language-javascript">true</code>, Chosen will not display the search field (single selects only).</td>
</tr>
<tr>
<td>disable_search_threshold</td>
<td>0</td>
<td>Hide the search input on single selects if there are <i>n</i> or fewer options.</td>
</tr>
<tr>
<td>enable_split_word_search</td>
<td>true</td>
<td>By default, searching will match on any word within an option tag. Set this option to <code class="language-javascript">false</code> if you want to only match on the entire text of an option tag.</td>
</tr>
<tr>
<td>inherit_select_classes</td>
<td>false</td>
<td>When set to <code class="language-javascript">true</code>, Chosen will grab any classes on the original select field and add them to Chosens container div.</td>
</tr>
<tr>
<td>max_selected_options</td>
<td>Infinity</td>
<td>Limits how many options the user can select. When the limit is reached, the <code class="language-javascript">chosen:maxselected</code> event is triggered.</td>
</tr>
<tr>
<td>no_results_text</td>
<td>"No results match"</td>
<td>The text to be displayed when no matching results are found. The current search is shown at the end of the text (<i>e.g.</i>,
No results match "Bad Search").</td>
</tr>
<tr>
<td>placeholder_text_multiple</td>
<td>"Select Some Options"</td>
<td>The text to be displayed as a placeholder when no options are selected for a multiple select.</td>
</tr>
<tr>
<td>placeholder_text_single</td>
<td>"Select an Option"</td>
<td>The text to be displayed as a placeholder when no options are selected for a single select.</td>
</tr>
<tr>
<td>search_contains</td>
<td>false</td>
<td>By default, Chosens search matches starting at the beginning of a word. Setting this option to <code class="language-javascript">true</code> allows matches starting from anywhere within a word. This is especially useful for options that include a lot of special characters or phrases in ()s and []s.</td>
</tr>
<tr>
<td>single_backstroke_delete</td>
<td>true</td>
<td>By default, pressing delete/backspace on multiple selects will remove a selected choice. When <code class="language-javascript">false</code>, pressing delete/backspace will highlight the last choice, and a second press deselects it.</td>
</tr>
<tr>
<td>width</td>
<td>Original select width.</td>
<td>The width of the Chosen select box. By default, Chosen attempts to match the width of the select box you are replacing. If your select is hidden when Chosen is instantiated, you must specify a width or the select will show up with a width of 0.</td>
</tr>
<tr>
<td>display_disabled_options</td>
<td>true</td>
<td>By default, Chosen includes disabled options in search results with a special styling. Setting this option to false will hide disabled results and exclude them from searches.</td>
</tr>
<tr>
<td>display_selected_options</td>
<td>true</td>
<td>
<p>By default, Chosen includes selected options in search results with a special styling. Setting this option to false will hide selected results and exclude them from searches.</p>
<p><strong>Note:</strong> this is for multiple selects only. In single selects, the selected result will always be displayed.</p>
</td>
</tr>
<tr>
<td>include_group_label_in_selected</td>
<td>false</td>
<td>
<p>By default, Chosen only shows the text of a selected option. Setting this option to <code class="language-javascript">true</code> will show the text and group (if any) of the selected option.</p>
</td>
</tr>
<tr>
<td>max_shown_results</td>
<td>Infinity</td>
<td>
<p>Only show the first (n) matching options in the results. This can be used to increase performance for selects with very many options.</p>
</td>
</tr>
<tr>
<td>case_sensitive_search</td>
<td>false</td>
<td>
<p>By default Chosen's search is case-insensitive. Setting this option to <code class="language-javascript">true</code> makes the search case-sensitive.</p>
</td>
</tr>
<tr>
<td>hide_results_on_select</td>
<td>true</td>
<td>
<p>By default Chosen's results are hidden after a option is selected. Setting this option to <code class="language-javascript">false</code> will keep the results open after selection. This only applies to multiple selects.</p>
</td>
</tr>
<tr>
<td>rtl</td>
<td>false</td>
<td>
<p>Chosen supports right-to-left text in select boxes. Set this option to <code class="language-javascript">true</code> to support right-to-left text options.</p>
<p><strong>Note:</strong> <a href="#classes">the <code class="language-javascript">chosen-rtl</code> class</a> on the select has precedence over this option. However, the classname approach is deprecated and will be removed in future versions of Chosen.</p>
</td>
</tr>
</table>
<h2><a name="attributes" class="anchor" href="#attributes">Attributes</a></h2>
<p>Certain attributes placed on the select tag or its options can be used to configure Chosen.</p>
<h3>Example:</h3>
<pre>
<code class="language-markup">&lt;select class="my_select_box" data-placeholder="Select Your Options"&gt;
&lt;option value="1"&gt;Option 1&lt;/option&gt;
&lt;option value="2" selected&gt;Option 2&lt;/option&gt;
&lt;option value="3" disabled&gt;Option 3&lt;/option&gt;
&lt;/select&gt;</code>
</pre>
<table class="docs-table">
<tr>
<th>Attribute</th><th>Description</th>
</tr>
<tr>
<td>data-placeholder</td>
<td>
<p>The text to be displayed as a placeholder when no options are selected for a select. Defaults to "Select an Option" for single selects or "Select Some Options" for multiple selects.</p>
<p><strong>Note:</strong>This attribute overrides anything set in the <code class="language-javascript">placeholder_text_multiple</code> or <code class="language-javascript">placeholder_text_single</code> options.</p>
</td>
</tr>
<tr>
<td>multiple</td>
<td>The attribute <code class="language-html">multiple</code> on your select box dictates whether Chosen will render a multiple or single select.</td>
</tr>
<tr>
<td>selected, disabled</td>
<td>Chosen automatically highlights selected options and disables disabled options.</td>
</tr>
</table>
<h2><a name="classes" class="anchor" href="#classes">Classes</a></h2>
<p>Classes placed on the select tag can be used to configure Chosen.</p>
<h3>Example:</h3>
<pre>
<code class="language-markup">&lt;select class="my_select_box chosen-rtl"&gt;
&lt;option value="1"&gt;Option 1&lt;/option&gt;
&lt;option value="2"&gt;Option 2&lt;/option&gt;
&lt;option value="3"&gt;Option 3&lt;/option&gt;
&lt;/select&gt;</code>
</pre>
<table class="docs-table">
<tr>
<th>Classname</th>
<th>Description</th>
</tr>
<tr>
<td>chosen-rtl</td>
<td>
<p>Chosen supports right-to-left text in select boxes. Add the class <code class="language-html">chosen-rtl</code> to your select tag to support right-to-left text options.</p>
<p><strong>Note:</strong> The <code class="language-html">chosen-rtl</code> class will pass through to the Chosen select even when the <code class="language-javascript">inherit_select_classes</code> option is set to <code class="language-javascript">false</code>.</p>
<p><strong>Note:</strong> This is deprecated in favor of using the <code class="language-javascript">rtl: true</code> option (see the <a href="#options">Options section</a>).</p>
</td>
</tr>
</table>
<h2><a name="triggered-events" class="anchor" href="#triggered-events">Triggered Events</a></h2>
<p>Chosen triggers a number of standard and custom events on the original select field.</p>
<h3>Example:</h3>
<pre>
<code class="language-javascript">$('.my_select_box').on('change', function(evt, params) {
do_something(evt, params);
});</code>
</pre>
<table class="docs-table">
<tr>
<th>Event</th><th>Description</th>
</tr>
<tr>
<td>change</td>
<td>
<p>Chosen triggers the standard DOM event whenever a selection is made (it also sends a <code class="language-javascript">selected</code> or <code class="language-javascript">deselected</code> parameter that tells you which option was changed).</p>
<p><strong>Note:</strong> The selected and deselected parameters are not available for Prototype.</p>
</td>
</tr>
<tr>
<td>chosen:ready</td>
<td>Triggered after Chosen has been fully instantiated.</td>
</tr>
<tr>
<td>chosen:maxselected</td>
<td>Triggered if <code class="language-javascript">max_selected_options</code> is set and that total is broken.</td>
</tr>
<tr>
<td>chosen:showing_dropdown</td>
<td>Triggered when Chosens dropdown is opened.</td>
</tr>
<tr>
<td>chosen:hiding_dropdown</td>
<td>Triggered when Chosens dropdown is closed.</td>
</tr>
<tr>
<td>chosen:no_results</td>
<td>Triggered when a search returns no matching results.</td>
</tr>
</table>
<p>
<strong>Note:</strong> all custom Chosen events (those that begin with <code class="language-javascript">chosen:</code>) also include the <code class="language-javascript">chosen</code> object as a parameter.
</p>
<h2><a name="triggerable-events" class="anchor" href="#triggerable-events">Triggerable Events</a></h2>
<p>You can trigger several events on the original select field to invoke a behavior in Chosen.</p>
<h3>Example:</h3>
<pre>
<code class="language-javascript">// tell Chosen that a select has changed
$('.my_select_box').trigger('chosen:updated');</code>
</pre>
<table class="docs-table">
<tr>
<th>Event</th><th>Description</th>
</tr>
<tr>
<td>chosen:updated</td>
<td>This event should be triggered whenever Chosens underlying select element changes (such as a change in selected options).</td>
</tr>
<tr>
<td>chosen:activate</td>
<td>This is the equivalant of focusing a standard HTML select field. When activated, Chosen will capure keypress events as if you had clicked the field directly.</td>
</tr>
<tr>
<td>chosen:open</td>
<td>This event activates Chosen and also displays the search results.</td>
</tr>
<tr>
<td>chosen:close</td>
<td>This event deactivates Chosen and hides the search results.</td>
</tr>
</table>
<footer>
&copy; 2011&ndash;2016 <a href="http://www.getharvest.com/">Harvest</a>. Chosen is licensed under the <a href="https://github.com/harvesthq/chosen/blob/master/LICENSE.md">MIT license</a>.
</footer>
</div>
</div>
<div class="oss-bar">
<ul>
<li><a class="fork" href="https://github.com/harvesthq/chosen">Fork on Github</a></li>
<li><a class="harvest" href="http://www.getharvest.com/">Built by Harvest</a></li>
</ul>
</div>
<script src="docsupport/prism.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>

View File

@ -1,50 +0,0 @@
{
"name": "chosen-js",
"version": "1.8.1",
"description": "Chosen is a JavaScript plugin that makes select boxes user-friendly. It is currently available in both jQuery and Prototype flavors.",
"keywords": [
"select",
"multiselect",
"dropdown",
"form",
"input",
"ui"
],
"homepage": "https://harvesthq.github.io/chosen/",
"bugs": "https://github.com/harvesthq/chosen/issues",
"license": "MIT",
"contributors": [
{
"name": "Patrick Filler",
"url": "https://github.com/pfiller"
},
{
"name": "Christophe Coevoet",
"url": "https://github.com/stof"
},
{
"name": "Ken Earley",
"url": "https://github.com/kenearley"
},
{
"name": "Koen Punt",
"url": "https://github.com/koenpunt"
}
],
"dependencies": {},
"files": [
"chosen.jquery.js",
"chosen.jquery.min.js",
"chosen.proto.js",
"chosen.proto.min.js",
"chosen.css",
"chosen.min.css",
"chosen-sprite@2x.png",
"chosen-sprite.png"
],
"main": "chosen.jquery.js",
"repository": {
"type": "git",
"url": "https://github.com/harvesthq/chosen.git"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -199,7 +199,7 @@ if ( currentView != 'none' && currentView != 'login' ) {
console.log( "Request Failed: " + err );
// The idea is that this should only fail due to auth, so reload the page
// which should go to login if it can't stay logged in.
window.location.href = thisUrl;
window.location.href = thisUrl+'?view='+currentView;
});
}

View File

@ -318,7 +318,7 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) {
<?php
}
?>
<td class="colZones"><?php echo makePopupLink( '?view=zones&amp;mid='.$monitor['Id'], 'zmZones', array( 'zones', $monitor['Width'], $monitor['Height'] ), $monitor['ZoneCount'], $running && canView('Monitors') ) ?></td>
<td class="colZones"><?php echo makePopupLink('?view=zones&amp;mid='.$monitor['Id'], 'zmZones', array('zones', $monitor['Width'], $monitor['Height']), $monitor['ZoneCount'], canView('Monitors')) ?></td>
<?php
if ( canEdit('Monitors') ) {
?>

View File

@ -77,14 +77,17 @@ xhtmlHeaders(__FILE__, translate('CycleWatch'));
<?php } ?>
</div>
<div class="controlHeader">
<form method="get">
<input type="hidden" name="view" value="cycle"/>
<?php echo $filterbar ?>
</form>
</div>
</div>
<div id="content">
<div id="imageFeed">
<?php
if ( $monitor ) {
echo getStreamHTML($monitor, array('scale'=>$scale, 'mode'=>$mode));
echo getStreamHTML($monitor, array('scale'=>$scale, 'mode'=>$mode, 'width'=>'100%'));
} else {
echo "There are no monitors to view.";
}

View File

@ -410,22 +410,22 @@ if ( ZM_OPT_MESSAGE ) {
</div>
<hr/>
<div id="contentButtons">
<button onclick="submitToEvents(this);"> <?php echo translate('ListMatches') ?></button>
<button name="executeButton" id="executeButton" onclick="executeFilter( this );"><?php echo translate('Execute') ?></button>
<button type="submit" onclick="submitToEvents(this);"><?php echo translate('ListMatches') ?></button>
<button type="submit" name="executeButton" id="executeButton" onclick="executeFilter( this );"><?php echo translate('Execute') ?></button>
<?php
if ( canEdit( 'Events' ) ) {
?>
<button name="Save" value="Save" onclick="saveFilter(this);"><?php echo translate('Save') ?></button>
<button name="SaveAs" value="SaveAs" onclick="saveFilter(this);"><?php echo translate('SaveAs') ?></button>
<button type="submit" name="Save" value="Save" onclick="saveFilter(this);"><?php echo translate('Save') ?></button>
<button type="submit" name="SaveAs" value="SaveAs" onclick="saveFilter(this);"><?php echo translate('SaveAs') ?></button>
<?php
if ( $filter->Id() ) {
?>
<input type="button" value="<?php echo translate('Delete') ?>" onclick="deleteFilter( this, '<?php echo $filter->Name() ?>' );"/>
<button type="button" value="Delete" onclick="deleteFilter(this, '<?php echo $filter->Name() ?>');"><?php echo translate('Delete') ?></button>
<?php
}
}
?>
<button value="<?php echo translate('Reset') ?>" onclick="resetFilter( this );"><?php echo translate('Reset') ?></button>
<button type="button" value="Reset" onclick="resetFilter( this );"><?php echo translate('Reset') ?></button>
</div>
</form>
</div><!--content-->

View File

@ -122,28 +122,28 @@ echo "\nvar Storage = [];\n";
foreach ( Storage::find_all() as $Storage ) {
echo 'Storage[' . $Storage->Id() . '] = ' . json_encode($Storage). ";\n";
}
echo "\nvar Servers = []\n";
echo "\nvar Servers = [];\n";
foreach ( Server::find_all() as $Server ) {
echo 'Servers[' . $Server->Id() . '] = new Server(' . json_encode($Server). ");\n";
}
echo "
var monitorName = [];\n";
echo "var monitorLoading = [];\n";
echo "var monitorImageObject = [];\n";
echo "var monitorImageURL = [];\n";
echo "var monitorLoadingStageURL = [];\n";
echo "var monitorLoadStartTimems = [];\n";
echo "var monitorLoadEndTimems = [];\n";
echo "var monitorColour = [];\n";
echo "var monitorWidth = [];\n";
echo "var monitorHeight = [];\n";
echo "var monitorIndex = [];\n";
echo "var monitorNormalizeScale = [];\n";
echo "var monitorZoomScale = [];\n";
echo "var monitorCanvasObj = [];\n"; // stash location of these here so we don't have to search
echo "var monitorCanvasCtx = [];\n";
echo "var monitorPtr = []; // monitorName[monitorPtr[0]] is first monitor\n";
echo '
var monitorName = [];
var monitorLoading = [];
var monitorImageObject = [];
var monitorImageURL = [];
var monitorLoadingStageURL = [];
var monitorLoadStartTimems = [];
var monitorLoadEndTimems = [];
var monitorColour = [];
var monitorWidth = [];
var monitorHeight = [];
var monitorIndex = [];
var monitorNormalizeScale = [];
var monitorZoomScale = [];
var monitorCanvasObj = [];
var monitorCanvasCtx = [];
var monitorPtr = []; // monitorName[monitorPtr[0]] is first monitor
';
$numMonitors=0; // this array is indexed by the monitor ID for faster access later, so it may be sparse
$avgArea=floatval(0); // Calculations the normalizing scale
@ -175,9 +175,11 @@ foreach ( $monitors as $m ) {
echo " monitorPtr[" . $numMonitors . "]=" . $m->Id() . ";\n";
$numMonitors += 1;
}
echo "var numMonitors = $numMonitors;\n";
echo "var minTimeSecs=parseInt(" . $minTimeSecs . ");\n";
echo "var maxTimeSecs=parseInt(" . $maxTimeSecs . ");\n";
echo "
var numMonitors = $numMonitors;
var minTimeSecs=parseInt($minTimeSecs);
var maxTimeSecs=parseInt($maxTimeSecs);
";
echo "var rangeTimeSecs=" . ( $maxTimeSecs - $minTimeSecs + 1) . ";\n";
if(isset($defaultCurrentTime))
echo "var currentTimeSecs=parseInt(" . strtotime($defaultCurrentTime) . ");\n";
@ -197,4 +199,3 @@ var canvas; // global canvas definition so we don't have to keep looking it up
var ctx;
var underSlider; // use this to hold what is hidden by the slider
var underSliderX; // Where the above was taken from (left side, Y is zero)

View File

@ -173,7 +173,7 @@ if ( !empty($_REQUEST['preset']) ) {
}
}
if ( !empty($_REQUEST['probe']) ) {
$probe = unserialize(base64_decode($_REQUEST['probe']));
$probe = unserialize($_REQUEST['probe']);
foreach ( $probe as $name=>$value ) {
if ( isset($value) ) {
# Does isset handle NULL's? I don't think this code is correct.
@ -925,6 +925,8 @@ if ( $monitor->Type() == 'Local' ) {
);
if ($monitor->Type() == 'Ffmpeg' )
$videowriteropts[2] = 'H264 Camera Passthrough';
else
$videowriteropts[2] = array('text'=>'H264 Camera Passthrough - only for FFMPEG','disabled'=>1);
echo htmlselect( 'newMonitor[VideoWriter]', $videowriteropts, $monitor->VideoWriter() );
?>
</td></tr>

View File

@ -18,67 +18,70 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
if ( !canEdit( 'Monitors' ) )
{
$view = "error";
if ( !canEdit('Monitors') ) {
$view = 'error';
return;
}
$cameras = array();
$cameras[0] = translate('ChooseDetectedCamera');
if ( ZM_HAS_V4L2 )
{
// Probe Local Cameras
//
$command = getZmuCommand( " --query --device" );
function probeV4L() {
$cameras = array();
$command = getZmuCommand(' --query --device');
if ( !empty($_REQUEST['device']) )
$command .= "=".escapeshellarg($_REQUEST['device']);
$command .= '='.escapeshellarg($_REQUEST['device']);
$result = exec(escapeshellcmd($command), $output, $status);
if ( $status )
Fatal( "Unable to probe local cameras, status is '$status'" );
if ( $status ) {
Error("Unable to probe local cameras, status is '$status'");
return $cameras;
}
$monitors = array();
foreach ( dbFetchAll( "select Id, Name, Device,Channel from Monitors where Type = 'Local' order by Device, Channel" ) as $monitor )
foreach ( dbFetchAll("SELECT Id, Name, Device,Channel FROM Monitors WHERE Type = 'Local' ORDER BY Device, Channel" ) as $monitor )
$monitors[$monitor['Device'].':'.$monitor['Channel']] = $monitor;
$devices = array();
$preferredStandards = array('PAL', 'NTSC');
$preferredFormats = array('BGR3', 'RGB3', 'YUYV', 'UYVY', 'JPEG', 'MJPG', '422P', 'YU12', 'GREY');
foreach ( $output as $line )
{
foreach ( $output as $line ) {
if ( !preg_match('/^d:([^|]+).*S:([^|]*).*F:([^|]+).*I:(\d+)\|(.+)$/', $line, $deviceMatches) )
Fatal("Can't parse command output '$line'");
$standards = explode('/',$deviceMatches[2]);
$preferredStandard = false;
foreach ( $preferredStandards as $standard )
{
if ( in_array( $standard, $standards ) )
{
foreach ( $preferredStandards as $standard ) {
if ( in_array( $standard, $standards ) ) {
$preferredStandard = $standard;
break;
}
}
$formats = explode('/',$deviceMatches[3]);
$preferredFormat = false;
foreach ( $preferredFormats as $format )
{
if ( in_array( $format, $formats ) )
{
foreach ( $preferredFormats as $format ) {
if ( in_array($format, $formats) ) {
$preferredFormat = $format;
break;
}
}
$device = array( 'device'=>$deviceMatches[1], 'standards'=>$standard, 'preferredStandard'=>$preferredStandard, 'formats'=>$formats, 'preferredFormat'=>$preferredFormat );
$device = array(
'device' => $deviceMatches[1],
'standards' => $standard,
'preferredStandard' => $preferredStandard,
'formats' => $formats,
'preferredFormat' => $preferredFormat,
);
$inputs = array();
for ( $i = 0; $i < $deviceMatches[4]; $i++ )
{
for ( $i = 0; $i < $deviceMatches[4]; $i++ ) {
if ( !preg_match('/i'.$i.':([^|]+)\|i'.$i.'T:([^|]+)\|/', $deviceMatches[5], $inputMatches) )
Fatal("Can't parse input '".$deviceMatches[5]."'");
if ( $inputMatches[2] == 'Camera' )
{
$input = array( 'index'=>$i, 'id'=>$deviceMatches[1].':'.$i, 'name'=>$inputMatches[1], 'free'=>empty($monitors[$deviceMatches[1].':'.$i]) );
if ( $inputMatches[2] == 'Camera' ) {
$input = array(
'index' => $i,
'id' => $deviceMatches[1].':'.$i,
'name' => $inputMatches[1],
'free' => empty($monitors[$deviceMatches[1].':'.$i]),
);
$inputMonitor = array(
'Type' => 'Local',
'Device' => $deviceMatches[1],
@ -87,39 +90,35 @@ if ( ZM_HAS_V4L2 )
'Format' => $preferredStandard,
'Palette' => $preferredFormat,
);
if ( $preferredStandard == 'NTSC' )
{
if ( $preferredStandard == 'NTSC' ) {
$inputMonitor['Width'] = 320;
$inputMonitor['Height'] = 240;
}
else
{
} else {
$inputMonitor['Width'] = 384;
$inputMonitor['Height'] = 288;
}
if ( $preferredFormat == 'GREY' )
{
if ( $preferredFormat == 'GREY' ) {
$inputMonitor['Colours'] = 1;
$inputMonitor['SignalCheckColour'] = '#000023';
}
$inputDesc = base64_encode(serialize($inputMonitor));
$inputString = $deviceMatches[1].', chan '.$i.($input['free']?(" - ".translate('Available')):(" (".$monitors[$input['id']]['Name'].")"));
$inputString = $deviceMatches[1].', chan '.$i.($input['free']?(' - '.translate('Available')):(' ('.$monitors[$input['id']]['Name'].')'));
$inputs[] = $input;
$cameras[$inputDesc] = $inputString;
}
}
$device['inputs'] = $inputs;
$devices[] = $device;
}
}
} # end foreach output line
return $cameras;
} # end function probeV4L
// Probe Network Cameras
//
function probeAxis( $ip )
{
function probeAxis($ip) {
$url = 'http://'.$ip.'/axis-cgi/admin/param.cgi?action=list&group=Brand';
$camera = array(
'model' => "Unknown Axis Camera",
'model' => 'Unknown Axis Camera',
'monitor' => array(
'Type' => 'Remote',
'Protocol' => 'http',
@ -131,29 +130,24 @@ function probeAxis( $ip )
'Height' => 240,
),
);
if ( $lines = @file( $url ) )
{
foreach ( $lines as $line )
{
if ( $lines = @file($url) ) {
foreach ( $lines as $line ) {
$line = rtrim( $line );
if ( preg_match( '/^(.+)=(.+)$/', $line, $matches ) )
{
if ( $matches[1] == 'root.Brand.ProdShortName' )
{
if ( preg_match('/^(.+)=(.+)$/', $line, $matches) ) {
if ( $matches[1] == 'root.Brand.ProdShortName' ) {
$camera['model'] = $matches[2];
break;
}
}
}
}
return( $camera );
return $camera;
}
function probePana( $ip )
{
function probePana($ip) {
$url = 'http://'.$ip.'/Get?Func=Model&Kind=1';
$camera = array(
'model' => "Unknown Panasonic Camera",
'model' => 'Unknown Panasonic Camera',
'monitor' => array(
'Type' => 'Remote',
'Protocol' => 'http',
@ -165,14 +159,13 @@ function probePana( $ip )
'Height' => 240,
),
);
return( $camera );
return $camera;
}
function probeActi( $ip )
{
function probeActi($ip) {
$url = 'http://'.$ip.'/cgi-bin/system?USER=Admin&PWD=123456&SYSTEM_INFO';
$camera = array(
'model' => "Unknown Panasonic Camera",
'model' => 'Unknown Panasonic Camera',
'monitor' => array(
'Type' => 'Remote',
'Protocol' => 'rtsp',
@ -185,29 +178,24 @@ function probeActi( $ip )
'Height' => 240,
),
);
if ( $lines = @file( $url ) )
{
foreach ( $lines as $line )
{
if ( $lines = @file($url) ) {
foreach ( $lines as $line ) {
$line = rtrim( $line );
if ( preg_match( '/^(.+?)\s*=\s*(.+)$/', $line, $matches ) )
{
if ( $matches[1] == 'Production ID' )
{
$camera['model'] = "ACTi ".substr( $matches[2], 0, strpos( $matches[2], '-' ));
if ( preg_match('/^(.+?)\s*=\s*(.+)$/', $line, $matches) ) {
if ( $matches[1] == 'Production ID' ) {
$camera['model'] = 'ACTi '.substr($matches[2], 0, strpos($matches[2], '-'));
break;
}
}
}
}
return( $camera );
return $camera;
}
function probeVivotek( $ip )
{
function probeVivotek($ip) {
$url = 'http://'.$ip.'/cgi-bin/viewer/getparam.cgi';
$camera = array(
'model' => "Unknown Vivotek Camera",
'model' => 'Unknown Vivotek Camera',
'monitor' => array(
'Type' => 'Remote',
'Protocol' => 'rtsp',
@ -220,35 +208,26 @@ function probeVivotek( $ip )
'Height' => 240,
),
);
if ( $lines = @file( $url ) )
{
foreach ( $lines as $line )
{
if ( $lines = @file($url) ) {
foreach ( $lines as $line ) {
$line = rtrim($line);
if ( preg_match( '/^(.+?)\s*=\'(.+)\'$/', $line, $matches ) )
{
if ( $matches[1] == 'system_info_modelname' )
{
$camera['model'] = "Vivotek ".$matches[2];
}
elseif ( $matches[1] == 'network_rtsp_port' )
{
if ( preg_match('/^(.+?)\s*=\'(.+)\'$/', $line, $matches) ) {
if ( $matches[1] == 'system_info_modelname' ) {
$camera['model'] = 'Vivotek '.$matches[2];
} elseif ( $matches[1] == 'network_rtsp_port' ) {
$camera['monitor']['Port'] = $matches[2];
}
elseif ( $matches[1] == 'network_rtsp_s0_accessname' )
{
} elseif ( $matches[1] == 'network_rtsp_s0_accessname' ) {
$camera['monitor']['Path'] = $matches[2];
}
}
}
}
return( $camera );
return $camera;
}
function probeWansview( $ip )
{
function probeWansview($ip) {
$camera = array(
'model' => "Wansview Camera",
'model' => 'Wansview Camera',
'monitor' => array(
'Type' => 'Remote',
'Protocol' => 'http',
@ -260,23 +239,35 @@ function probeWansview( $ip )
'Palette' => 3
),
);
return( $camera );
return $camera;
}
function probeNetwork() {
$cameras = array();
$arp_command = ZM_PATH_ARP;
$result = explode(' ', $arp_command);
if ( !is_executable($result[0]) ) {
Error("ARP compatible binary not found or not executable by the web user account. Verify ZM_PATH_ARP points to a valid arp tool.");
return;
}
$result = exec(escapeshellcmd($arp_command), $output, $status);
if ( $status ) {
Error("Unable to probe network cameras, status is '$status'");
return;
}
$monitors = array();
foreach ( dbFetchAll( "select Id, Name, Host from Monitors where Type = 'Remote' order by Host" ) as $monitor )
{
if ( preg_match( '/^(.+)@(.+)$/', $monitor['Host'], $matches ) )
{
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])."<br/>";
$monitors[gethostbyname($matches[2])] = $monitor;
}
else
{
} else {
//echo "2: ".$monitor['Host']." = ".gethostbyname($monitor['Host'])."<br/>";
$monitors[gethostbyname($monitor['Host'])] = $monitor;
}
}
$macBases = array(
'00:40:8c' => array('type'=>'Axis', 'probeFunc'=>'probeAxis'),
'00:80:f0' => array('type'=>'Panasonic','probeFunc'=>'probePana'),
@ -286,41 +277,7 @@ $macBases = array(
'78:a5:dd' => array('type'=>'Wansview','probeFunc'=>'probeWansview')
);
unset($output);
// Calling arp without the full path was reported to fail on some systems
// Use the builtin unix command "type" to tell us where the command is
$arp_command = '';
$result = explode( " ", ZM_PATH_ARP );
if ( !is_executable( $result[0] ) ) {
if ( ZM_PATH_ARP ) {
Warning( "User assigned ARP tool not found. Verify ZM_PATH_ARP points to a valid arp tool and is executable by the web user account." );
}
$result = exec( 'type -p arp', $output, $status );
if ( $status ) {
Warning( "Unable to determine path for arp command, type -p arp returned '$status' output is: " . implode( "\n", $output ) );
unset($output);
$result = exec( 'which arp', $output, $status );
if ( $status ) {
Warning( "Unable to determine path for arp command, which arp returned '$status'" );
if ( file_exists( '/usr/sbin/arp' ) ) {
$arp_command = '/usr/sbin/arp -a';
}
} else {
$arp_command = $output[0]." -a";
}
} else {
$arp_command = $output[0]." -a";
}
} else {
$arp_command = ZM_PATH_ARP;
}
// Now that we know where arp is, call it using the full path
unset($output);
$result = exec( escapeshellcmd($arp_command), $output, $status );
if ( $status )
Fatal( "Unable to probe network cameras, status is '$status'" );
foreach ( $output as $line )
{
foreach ( $output as $line ) {
if ( !preg_match('/(\d+\.\d+\.\d+\.\d+).*(([0-9a-f]{2}:){5})/', $line, $matches) )
continue;
$ip = $matches[1];
@ -328,26 +285,31 @@ foreach ( $output as $line )
$mac = $matches[2];
//echo "I:$ip, H:$host, M:$mac<br/>";
$macRoot = substr($mac,0,8);
if ( isset($macBases[$macRoot]) )
{
if ( isset($macBases[$macRoot]) ) {
$macBase = $macBases[$macRoot];
$camera = call_user_func($macBase['probeFunc'], $ip);
$sourceDesc = htmlspecialchars(serialize($camera['monitor']));
$sourceString = $camera['model'].' @ '.$host;
if ( isset($monitors[$ip]) )
{
if ( isset($monitors[$ip]) ) {
$monitor = $monitors[$ip];
$sourceString .= " (".$monitor['Name'].")";
}
else
{
$sourceString .= " - ".translate('Available');
$sourceString .= ' ('.$monitor['Name'].')';
} else {
$sourceString .= ' - '.translate('Available');
}
$cameras[$sourceDesc] = $sourceString;
}
}
} # end foreach output line
return $cameras;
} # end function probeNetwork()
if ( count($cameras) <= 0 )
$cameras = array();
$cameras[0] = translate('ChooseDetectedCamera');
if ( ZM_HAS_V4L2 )
$cameras += probeV4L();
$cameras += probeNetwork();
if ( count($cameras) <= 1 )
$cameras[0] = translate('NoDetectedCameras');
$focusWindow = true;
@ -367,10 +329,13 @@ xhtmlHeaders(__FILE__, translate('MonitorProbe') );
<?php echo translate('MonitorProbeIntro') ?>
</p>
<p>
<label for="probe"><?php echo translate('DetectedCameras') ?></label><?php echo buildSelect( "probe", $cameras, 'configureButtons( this )' ); ?>
<label for="probe"><?php echo translate('DetectedCameras') ?></label>
<?php echo buildSelect('probe', $cameras, 'configureButtons(this)'); ?>
</p>
<div id="contentButtons">
<input type="submit" name="saveBtn" value="<?php echo translate('Save') ?>" onclick="submitCamera( this )" disabled="disabled"/><input type="button" value="<?php echo translate('Cancel') ?>" onclick="closeWindow()"/>
<button type="button" name="saveBtn" value="Save" onclick="submitCamera(this);" disabled="disabled">
<?php echo translate('Save') ?></button>
<button type="button" onclick="closeWindow();"><?php echo translate('Cancel') ?></button>
</div>
</form>
</div>

View File

@ -262,8 +262,8 @@ foreach( array_map( 'basename', glob('skins/'.$current_skin.'/css/*',GLOB_ONLYDI
<thead class="thead-highlight">
<tr>
<th class="colId"><?php echo translate('Id') ?></th>
<th class="colName"><?php echo translate('name') ?></th>
<th class="colPath"><?php echo translate('path') ?></th>
<th class="colName"><?php echo translate('Name') ?></th>
<th class="colPath"><?php echo translate('Path') ?></th>
<th class="colType"><?php echo translate('Type') ?></th>
<th class="colScheme"><?php echo translate('StorageScheme') ?></th>
<th class="colServer"><?php echo translate('Server') ?></th>