mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-03-26 09:42:42 -04:00
Rename Janus-specific restream fields to be more generic since they are now used by Go2RTC and RTSP2Web as well: - Janus_Use_RTSP_Restream → Restream - Janus_RTSP_User → RTSP_User Update visibility logic so the Restream checkbox appears when RTSPServer is enabled AND any streaming service (Janus, Go2RTC, or RTSP2Web) is selected, rather than only when Janus is enabled. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
366 lines
14 KiB
PHP
366 lines
14 KiB
PHP
<?php
|
|
//
|
|
// ZoneMinder web action file
|
|
// Copyright (C) 2019 ZoneMinder LLC
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
// Monitor edit actions, monitor id derived, require edit permissions for that monitor
|
|
|
|
require_once('includes/Monitor.php');
|
|
require_once('includes/Zone.php');
|
|
|
|
global $error_message;
|
|
|
|
if ($action == 'save') {
|
|
$mid = 0;
|
|
if (!empty($_REQUEST['mid'])) {
|
|
$mid = validInt($_REQUEST['mid']);
|
|
if (!canEdit('Monitors', $mid)) {
|
|
ZM\Warning('You do not have permission to edit this monitor');
|
|
return;
|
|
}
|
|
if (ZM_OPT_X10) {
|
|
$x10Monitor = dbFetchOne('SELECT * FROM TriggersX10 WHERE MonitorId=?', NULL, array($mid));
|
|
if (!$x10Monitor) $x10Monitor = array();
|
|
}
|
|
} else {
|
|
if (!canCreate('Monitors')) {
|
|
ZM\Warning('Monitor actions require Monitors Create Permissions');
|
|
return;
|
|
}
|
|
if ($user->unviewableMonitorIds()) {
|
|
ZM\Warning('You are restricted to certain monitors so cannot add a new one.');
|
|
return;
|
|
}
|
|
if (ZM_OPT_X10) {
|
|
$x10Monitor = array();
|
|
}
|
|
if (!empty($_REQUEST['newMonitor']['Id'])) {
|
|
// Reuse existing but deleted monitor
|
|
$mid = validCardinal($_REQUEST['newMonitor']['Id']);
|
|
}
|
|
}
|
|
|
|
# For convenience
|
|
$newMonitor = $_REQUEST['newMonitor'];
|
|
|
|
if (!$newMonitor['ManufacturerId'] and ($newMonitor['Manufacturer'] != '')) {
|
|
# Need to add a new Manufacturer entry
|
|
$newManufacturer = ZM\Manufacturer::find_one(array('Name'=>$newMonitor['Manufacturer']));
|
|
if (!$newManufacturer) {
|
|
$newManufacturer = new ZM\Manufacturer();
|
|
if (!$newManufacturer->save(array('Name'=>$newMonitor['Manufacturer']))) {
|
|
$error_message .= "Error saving new Manufacturer: " . $newManufacturer->get_last_error().'</br>';
|
|
}
|
|
}
|
|
$newMonitor['ManufacturerId'] = $newManufacturer->Id();
|
|
unset($newMonitor['Manufacturer']);
|
|
}
|
|
|
|
if (!$newMonitor['ModelId'] and ($newMonitor['Model'] != '')) {
|
|
# Need to add a new Model entry
|
|
$newModel = ZM\Model::find_one(array('Name'=>$newMonitor['Model']));
|
|
if (!$newModel) {
|
|
$newModel = new ZM\Model();
|
|
if (!$newModel->save(array(
|
|
'Name'=>$newMonitor['Model'],
|
|
'ManufacturerId'=>$newMonitor['ManufacturerId']
|
|
))) {
|
|
$error_message .= "Error saving new Model: " . $newModel->get_last_error().'</br>';
|
|
}
|
|
}
|
|
$newMonitor['ModelId'] = $newModel->Id();
|
|
unset($newMonitor['Model']);
|
|
}
|
|
|
|
$monitor = new ZM\Monitor($mid);
|
|
|
|
// Define a field type for anything that's not simple text equivalent
|
|
$types = array(
|
|
'Triggers' => array(),
|
|
'Controllable' => 0,
|
|
'TrackMotion' => 0,
|
|
'ModectDuringPTZ' => 0,
|
|
'Enabled' => 0,
|
|
'Deleted' => 0,
|
|
'DecodingEnabled' => 0,
|
|
'RTSP2WebEnabled' => 0,
|
|
'Go2RTCEnabled' => 0,
|
|
'JanusEnabled' => 0,
|
|
'JanusAudioEnabled' => 0,
|
|
'Restream' => 0,
|
|
// 'Janus_RTSP_Session_Timeout' => 0,
|
|
'Exif' => 0,
|
|
'RTSPDescribe' => 0,
|
|
'V4LMultiBuffer' => '',
|
|
'WallClockTimestamps' => '',
|
|
'RecordAudio' => 0,
|
|
'Method' => 'raw',
|
|
'GroupIds' => array(),
|
|
'LinkedMonitors' => array(),
|
|
'MQTT_Enabled' => 0,
|
|
'RTSPServer' => 0,
|
|
'SectionLengthWarn' => 0,
|
|
'SOAP_wsa_compl' => 0
|
|
);
|
|
|
|
# Checkboxes don't return an element in the POST data, so won't be present in newMonitor.
|
|
# So force a value for these fields
|
|
foreach ($types as $field => $value) {
|
|
if (!isset($newMonitor[$field])) {
|
|
$newMonitor[$field] = $value;
|
|
}
|
|
} # end foreach type
|
|
|
|
if (isset($newMonitor['ServerId']) and ($newMonitor['ServerId'] == 'auto')) {
|
|
$newMonitor['ServerId'] = dbFetchOne(
|
|
'SELECT Id FROM Servers WHERE Status=\'Running\' ORDER BY FreeMem DESC, CpuLoad ASC LIMIT 1', 'Id');
|
|
ZM\Debug('Auto selecting server: Got ' . $newMonitor['ServerId']);
|
|
if ((!$newMonitor['ServerId']) and defined('ZM_SERVER_ID')) {
|
|
$newMonitor['ServerId'] = ZM_SERVER_ID;
|
|
ZM\Debug('Auto selecting server to '.ZM_SERVER_ID);
|
|
}
|
|
}
|
|
if (!empty($newMonitor['ManufacturerId']) and empty($newMonitor['Manufacturer']))
|
|
unset($newMonitor['Manufacturer']);
|
|
if (!empty($newMonitor['ModelId']) and empty($newMonitor['Model']))
|
|
unset($newMonitor['Model']);
|
|
|
|
$changes = $monitor->changes($newMonitor);
|
|
ZM\Debug('Changes: '. print_r($changes, true));
|
|
$restart = false;
|
|
|
|
if (count($changes)) {
|
|
// monitor->Id() has a value when the db record exists
|
|
if ($monitor->Id()) {
|
|
if ($monitor->Deleted() and ! isset($_REQUEST['newMonitor[Deleted]'])) {
|
|
# We are saving a new monitor with a specified Id and the Id is used in a deleted record.
|
|
# Undelete it so that it is visible.
|
|
$monitor->Deleted(false);
|
|
}
|
|
|
|
# If we change anything that changes the shared mem size, zma can complain. So let's stop first.
|
|
if ($monitor->Type() != 'WebSite') {
|
|
$monitor->zmcControl('stop');
|
|
if ($monitor->Controllable()) {
|
|
$monitor->sendControlCommand('stop');
|
|
}
|
|
}
|
|
|
|
$oldMonitor = clone $monitor;
|
|
|
|
if ($monitor->save($changes)) {
|
|
# Leave old symlinks on old storage areas, as old events will still be there. Only delete the link if the name has changed
|
|
if (isset($changes['Name'])) {
|
|
$link_path = $oldMonitor->Storage()->Path().'/'.basename($oldMonitor->Name());
|
|
if (file_exists($link_path)) {
|
|
ZM\Debug("Deleting old link ".$link_path);
|
|
unlink($link_path);
|
|
} else {
|
|
ZM\Debug("Old link didn't exist at ".$link_path);
|
|
}
|
|
}
|
|
|
|
if (isset($changes['Width']) || isset($changes['Height'])) {
|
|
$newW = $newMonitor['Width'];
|
|
$newH = $newMonitor['Height'];
|
|
|
|
$zones = dbFetchAll('SELECT * FROM Zones WHERE MonitorId=?', NULL, array($mid));
|
|
|
|
if ( ($newW == $oldMonitor->Height()) and ($newH == $oldMonitor->Width()) ) {
|
|
foreach ( $zones as $zone ) {
|
|
$newZone = $zone;
|
|
# Rotation, no change to area etc just swap the coords
|
|
$newZone = $zone;
|
|
$points = coordsToPoints($zone['Coords']);
|
|
for ( $i = 0; $i < count($points); $i++ ) {
|
|
$x = $points[$i]['x'];
|
|
$points[$i]['x'] = $points[$i]['y'];
|
|
$points[$i]['y'] = $x;
|
|
|
|
if ( $points[$i]['x'] > ($newW-1) ) {
|
|
ZM\Warning("Correcting x {$points[$i]['x']} > $newW of zone {$newZone['Name']} as it extends outside the new dimensions");
|
|
$points[$i]['x'] = ($newW-1);
|
|
}
|
|
if ( $points[$i]['y'] > ($newH-1) ) {
|
|
ZM\Warning("Correcting y {$points[$i]['y']} $newH of zone {$newZone['Name']} as it extends outside the new dimensions");
|
|
$points[$i]['y'] = ($newH-1);
|
|
}
|
|
}
|
|
|
|
$newZone['Coords'] = pointsToCoords($points);
|
|
$changes = getFormChanges($zone, $newZone, $types);
|
|
|
|
if ( count($changes) ) {
|
|
dbQuery('UPDATE Zones SET '.implode(', ', $changes).' WHERE MonitorId=? AND Id=?',
|
|
array($mid, $zone['Id']));
|
|
}
|
|
} # end foreach zone
|
|
} else {
|
|
$newA = $newW * $newH;
|
|
$oldA = $oldMonitor->Width() * $oldMonitor->Height();
|
|
|
|
foreach ( $zones as $zone ) {
|
|
$newZone = $zone;
|
|
$points = coordsToPoints($zone['Coords']);
|
|
for ( $i = 0; $i < count($points); $i++ ) {
|
|
$points[$i]['x'] = intval(($points[$i]['x']*($newW-1))/($oldMonitor->Width()-1));
|
|
$points[$i]['y'] = intval(($points[$i]['y']*($newH-1))/($oldMonitor->Height()-1));
|
|
if ( $points[$i]['x'] > ($newW-1) ) {
|
|
ZM\Warning("Correcting x of zone {$newZone['Name']} as it extends outside the new dimensions");
|
|
$points[$i]['x'] = ($newW-1);
|
|
}
|
|
if ( $points[$i]['y'] > ($newH-1) ) {
|
|
ZM\Warning("Correcting y of zone {$newZone['Name']} as it extends outside the new dimensions");
|
|
$points[$i]['y'] = ($newH-1);
|
|
}
|
|
}
|
|
$newZone['Coords'] = pointsToCoords($points);
|
|
$newZone['Area'] = intval(round(($zone['Area']*$newA)/$oldA));
|
|
$newZone['MinAlarmPixels'] = intval(round(($newZone['MinAlarmPixels']*$newA)/$oldA));
|
|
$newZone['MaxAlarmPixels'] = intval(round(($newZone['MaxAlarmPixels']*$newA)/$oldA));
|
|
$newZone['MinFilterPixels'] = intval(round(($newZone['MinFilterPixels']*$newA)/$oldA));
|
|
$newZone['MaxFilterPixels'] = intval(round(($newZone['MaxFilterPixels']*$newA)/$oldA));
|
|
$newZone['MinBlobPixels'] = intval(round(($newZone['MinBlobPixels']*$newA)/$oldA));
|
|
$newZone['MaxBlobPixels'] = intval(round(($newZone['MaxBlobPixels']*$newA)/$oldA));
|
|
|
|
$changes = getFormChanges($zone, $newZone, $types);
|
|
|
|
if ( count($changes) ) {
|
|
dbQuery('UPDATE Zones SET '.implode(', ', $changes).' WHERE MonitorId=? AND Id=?',
|
|
array($mid, $zone['Id']));
|
|
}
|
|
} // end foreach zone
|
|
} // end if rotation or just size change
|
|
} // end if changes in width or height
|
|
} else {
|
|
$error_message .= $monitor->get_last_error();
|
|
} // end if successful save
|
|
$restart = true;
|
|
} else { // new monitor
|
|
// Can only create new monitors if we are not restricted to specific monitors
|
|
# FIXME This is actually a race condition. Should lock the table.
|
|
$maxSeq = dbFetchOne('SELECT MAX(Sequence) AS MaxSequence FROM Monitors', 'MaxSequence');
|
|
$changes['Sequence'] = $maxSeq+1;
|
|
if ( $mid ) $changes['Id'] = $mid; # mid specified in request, doesn't exist in db, will re-use slot
|
|
|
|
|
|
if ( $monitor->insert($changes) ) {
|
|
$mid = $monitor->Id();
|
|
// Adjust zone dimensions if monitor has rotation applied
|
|
$zoneWidth = $newMonitor['Width'];
|
|
$zoneHeight = $newMonitor['Height'];
|
|
if (isset($newMonitor['Orientation']) &&
|
|
($newMonitor['Orientation'] == 'ROTATE_90' || $newMonitor['Orientation'] == 'ROTATE_270')) {
|
|
// Swap dimensions for 90/270 degree rotations
|
|
$zoneWidth = $newMonitor['Height'];
|
|
$zoneHeight = $newMonitor['Width'];
|
|
}
|
|
$zoneArea = $zoneWidth * $zoneHeight;
|
|
$zone = new ZM\Zone();
|
|
if (!$zone->save(['MonitorId'=>$monitor->Id(), 'Name'=>'All', 'Coords'=>
|
|
sprintf( '%d,%d %d,%d %d,%d %d,%d', 0, 0,
|
|
$zoneWidth-1,
|
|
0,
|
|
$zoneWidth-1,
|
|
$zoneHeight-1,
|
|
0,
|
|
$zoneHeight-1),
|
|
'Area'=>$zoneArea,
|
|
'MinAlarmPixels'=>intval(($zoneArea*.05)/100),
|
|
'MaxAlarmPixels'=>intval(($zoneArea*75)/100),
|
|
'MinFilterPixels'=>intval(($zoneArea*.05)/100),
|
|
'MaxFilterPixels'=>intval(($zoneArea*75)/100),
|
|
'MinBlobPixels'=>intval(($zoneArea*.05)/100)
|
|
])) {
|
|
$error_message .= $zone->get_last_error();
|
|
ZM\Error('Error adding zone:' . $error_message);
|
|
}
|
|
} else {
|
|
ZM\Error('Error saving new Monitor.');
|
|
return;
|
|
}
|
|
}
|
|
|
|
$Storage = $monitor->Storage();
|
|
$mid_dir = $Storage->Path().'/'.$mid;
|
|
if (!file_exists($mid_dir)) {
|
|
if (!@mkdir($mid_dir, 0755)) {
|
|
ZM\Error('Unable to mkdir '.$Storage->Path().'/'.$mid);
|
|
}
|
|
}
|
|
|
|
$saferName = basename($newMonitor['Name']);
|
|
$link_path = $Storage->Path().'/'.$saferName;
|
|
if (($saferName != $newMonitor['Name']) and !@symlink($mid, $link_path)) {
|
|
if (!(file_exists($link_path) and is_link($link_path))) {
|
|
ZM\Warning('Unable to symlink ' . $Storage->Path().'/'.$mid . ' to ' . $link_path);
|
|
}
|
|
}
|
|
|
|
if ( isset($changes['GroupIds']) ) {
|
|
dbQuery('DELETE FROM Groups_Monitors WHERE MonitorId=?', array($mid));
|
|
foreach ( $changes['GroupIds'] as $group_id ) {
|
|
dbQuery('INSERT INTO Groups_Monitors (GroupId, MonitorId) VALUES (?,?)', array($group_id, $mid));
|
|
}
|
|
} // end if there has been a change of groups
|
|
|
|
$restart = true;
|
|
} else {
|
|
ZM\Debug('No action due to no changes to Monitor');
|
|
} # end if count(changes)
|
|
|
|
if ( !$mid ) {
|
|
ZM\Error("We should have a mid by now. Something went wrong.");
|
|
return;
|
|
}
|
|
|
|
if ( ZM_OPT_X10 ) {
|
|
$x10Changes = getFormChanges($x10Monitor, $_REQUEST['newX10Monitor']);
|
|
|
|
if ( count($x10Changes) ) {
|
|
if ( $x10Monitor && isset($_REQUEST['newX10Monitor']) ) {
|
|
dbQuery('UPDATE TriggersX10 SET '.implode(', ', $x10Changes).' WHERE MonitorId=?', array($mid));
|
|
} else if ( !$user->unviewableMonitorIds() ) {
|
|
if ( !$x10Monitor ) {
|
|
dbQuery('INSERT INTO TriggersX10 SET MonitorId = ?, '.implode(', ', $x10Changes), array($mid));
|
|
} else {
|
|
dbQuery('DELETE FROM TriggersX10 WHERE MonitorId = ?', array($mid));
|
|
}
|
|
}
|
|
$restart = true;
|
|
} # end if has x10Changes
|
|
} # end if ZM_OPT_X10
|
|
|
|
if ( $restart ) {
|
|
if ( $monitor->Capturing() != 'None' and $monitor->Type() != 'WebSite' and !$monitor->Deleted()) {
|
|
$monitor->zmcControl('start');
|
|
|
|
if ( $monitor->Controllable() ) {
|
|
$monitor->sendControlCommand('start');
|
|
}
|
|
}
|
|
// really should thump zmwatch and maybe zmtrigger too.
|
|
//daemonControl( 'restart', 'zmwatch.pl' );
|
|
} // end if restart
|
|
if (!$error_message)
|
|
$redirect = '?view=console';
|
|
} else {
|
|
ZM\Warning("Unknown action $action in Monitor");
|
|
} // end if action == Delete
|
|
?>
|