mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-09-23 02:55:44 -04:00
zm_update-1.39.31.sql gave monitors audio detection, but the level only ever existed in shared memory, so it was gone the moment the frame passed and there was nothing for the event view to plot. Add Frames.AudioLevel next to Score, on the same 0-100 dBFS-derived scale the threshold uses. What is stored is the peak since the previous row, not the level at the instant the row was written. Frames rows are written well below the capture rate -- only alarm, bulk and score-increasing frames get one -- so sampling at write time would drop exactly the short loud noises worth seeing on a timeline. AudioDetector accumulates the peak as it decodes and Event::AddFrame takes it where the row is built, which clears it so each row covers its own interval. The Event constructor takes and discards it once, otherwise an event's first row reports the loudest moment since the previous event ended. This needs no shared memory change: zma is now an offline re-analysis tool and the live analysis runs in a thread of zmc, alongside the capture thread that runs the decoder, so the peak can stay in the AudioDetector. SharedData keeps its documented 888-byte layout and its fixed offsets. The frames ajax returns the column, and Score with it. elements in web/ajax/status.php is a whitelist that never listed Score, which is why the event view's cue strip has been reading an undefined Score off every frame. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JpiSWBmtQkR5bcgpHWY4ME
618 lines
22 KiB
PHP
618 lines
22 KiB
PHP
<?php
|
|
if (!isset($_REQUEST['entity'])) {
|
|
Error("No entity pass to status request.");
|
|
http_response_code(404);
|
|
return;
|
|
} else {
|
|
|
|
}
|
|
|
|
if ($_REQUEST['entity'] == 'navBar') {
|
|
global $bandwidth_options, $user;
|
|
$data = array();
|
|
if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') ) {
|
|
$auth_hash = generateAuthHash(ZM_AUTH_HASH_IPS);
|
|
$data['auth'] = $auth_hash;
|
|
$data['auth_relay'] = get_auth_relay();
|
|
}
|
|
// Each widget on the navbar has its own function
|
|
// Call the functions we want to dynamically update
|
|
$data['getBandwidthHTML'] = getBandwidthHTML($bandwidth_options, $user);
|
|
$data['getSysLoadHTML'] = getSysLoadHTML();
|
|
$data['getCpuUsageHTML'] = getCpuUsageHTML();
|
|
$data['getDbConHTML'] = getDbConHTML();
|
|
$data['getStorageHTML'] = getStorageHTML();
|
|
//$data['getShmHTML'] = getShmHTML();
|
|
$data['getRamHTML'] = getRamHTML();
|
|
$data['getLogStatusHTML'] = logState();
|
|
|
|
ajaxResponse($data);
|
|
return;
|
|
}
|
|
|
|
$statusData = array(
|
|
'system' => array(
|
|
'permission' => 'System',
|
|
'table' => 'Monitors',
|
|
'limit' => 1,
|
|
'elements' => array(
|
|
'MonitorCount' => array( 'sql' => 'count(*)' ),
|
|
'ActiveMonitorCount' => array( 'sql' => 'count(if(`Capturing` != \'None\',1,NULL))' ),
|
|
'State' => array( 'func' => function() { return daemonCheck() ? translate('Running') : translate('Stopped'); } ),
|
|
'Load' => array( 'func' => function() { return getLoad(); } ),
|
|
'Disk' => array( 'func' => function() { return getDiskPercent(); } ),
|
|
),
|
|
),
|
|
'monitor' => array(
|
|
#'permission' => 'Monitors',
|
|
'object' => 'Monitor',
|
|
'table' => 'Monitors',
|
|
'limit' => 1,
|
|
'selector' => 'Monitors.Id',
|
|
'elements' => array(
|
|
'Id' => array( 'sql' => 'Monitors.Id' ),
|
|
'Name' => array( 'sql' => 'Monitors.Name' ),
|
|
'Type' => true,
|
|
'Capturing' => true,
|
|
'Analysing' => true,
|
|
'Recording' => true,
|
|
'Enabled' => true,
|
|
'LinkedMonitors' => true,
|
|
'Triggers' => true,
|
|
'Device' => true,
|
|
'Channel' => true,
|
|
'Format' => true,
|
|
'Host' => true,
|
|
'Port' => true,
|
|
'Path' => true,
|
|
'Width' => array( 'sql' => 'Monitors.Width' ),
|
|
'Height' => array( 'sql' => 'Monitors.Height' ),
|
|
'Palette' => true,
|
|
'Orientation' => true,
|
|
'Brightness' => true,
|
|
'Contrast' => true,
|
|
'Hue' => true,
|
|
'Colour' => true,
|
|
'EventPrefix' => true,
|
|
'LabelFormat' => true,
|
|
'LabelX' => true,
|
|
'LabelY' => true,
|
|
'LabelSize' => true,
|
|
'ImageBufferCount' => true,
|
|
'WarmupCount' => true,
|
|
'PreEventCount' => true,
|
|
'PostEventCount' => true,
|
|
'AlarmFrameCount' => true,
|
|
'SectionLength' => true,
|
|
'MotionFrameSkip' => true,
|
|
'MaxFPS' => true,
|
|
'AlarmMaxFPS' => true,
|
|
'FPSReportInterval' => true,
|
|
'RefBlendPerc' => true,
|
|
'Controllable' => true,
|
|
'ControlId' => true,
|
|
'ControlDevice' => true,
|
|
'ControlAddress' => true,
|
|
'AutoStopTimeout' => true,
|
|
'TrackMotion' => true,
|
|
'TrackDelay' => true,
|
|
'ReturnLocation' => true,
|
|
'ReturnDelay' => true,
|
|
'DefaultView' => true,
|
|
'DefaultRate' => true,
|
|
'DefaultScale' => true,
|
|
'WebColour' => true,
|
|
'Sequence' => true,
|
|
'MinEventId' => array( 'sql' => '(SELECT min(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id)' ),
|
|
'MaxEventId' => array( 'sql' => '(SELECT max(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id)' ),
|
|
'TotalEvents' => array( 'sql' => '(SELECT count(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id)' ),
|
|
'Status' => (isset($_REQUEST['id'])?array( 'zmu' => '-m '.escapeshellarg($_REQUEST['id']).' -s' ):null),
|
|
'FrameRate' => (isset($_REQUEST['id'])?array( 'zmu' => '-m '.escapeshellarg($_REQUEST['id']).' -f' ):null),
|
|
'CaptureFPS' => [ 'sql'=>'(SELECT `CaptureFPS` FROM Monitor_Status WHERE MonitorId=Monitors.Id)' ],
|
|
'AnalysisFPS' => [ 'sql'=>'(SELECT `AnalysisFPS` FROM Monitor_Status WHERE MonitorId=Monitors.Id)' ],
|
|
'CaptureBandwidth' => [ 'sql'=>'(SELECT `CaptureBandwidth` FROM Monitor_Status WHERE MonitorId=Monitors.Id)' ],
|
|
),
|
|
),
|
|
'events' => array(
|
|
'permission' => 'Events',
|
|
'table' => 'Events',
|
|
'selector' => 'Events.MonitorId',
|
|
'elements' => array(
|
|
'Id' => true,
|
|
'MonitorId' => true,
|
|
'Name' => true,
|
|
'Cause' => true,
|
|
'Notes' => true,
|
|
'StartDateTime' => true,
|
|
'StartDateTimeFormatted' => array('postFunction'=>function($row){
|
|
global $dateTimeFormatter;
|
|
return $dateTimeFormatter->format(strtotime($row['StartDateTime']));
|
|
}),
|
|
# Left for backwards compatibility. Remove in 1.37
|
|
'EndDateTime' => true,
|
|
'EndDateTimeFormatted' => array('postFunction'=>function($row){
|
|
global $dateTimeFormatter;
|
|
return $dateTimeFormatter->format(strtotime($row['EndDateTime']));
|
|
}),
|
|
'Width' => true,
|
|
'Height' => true,
|
|
'Length' => true,
|
|
'Frames' => true,
|
|
'AlarmFrames' => true,
|
|
'TotScore' => true,
|
|
'AvgScore' => true,
|
|
'MaxScore' => true,
|
|
),
|
|
),
|
|
'event' => array(
|
|
'permission' => 'Events',
|
|
'table' => 'Events',
|
|
'limit' => 1,
|
|
'selector' => 'Events.Id',
|
|
'elements' => array(
|
|
'Id' => array( 'sql' => 'Events.Id' ),
|
|
'MonitorId' => true,
|
|
'MonitorName' => array('sql' => '(SELECT Monitors.Name FROM Monitors WHERE Monitors.Id = Events.MonitorId)'),
|
|
'Name' => true,
|
|
'Cause' => true,
|
|
'DiskSpace' => true,
|
|
'Storage' => array('sql' => '(SELECT Storage.Name FROM Storage WHERE Storage.Id=Events.StorageId)'),
|
|
'StartDateTime' => true,
|
|
'StartDateTimeFormatted' => array('postFunction'=>function($row){
|
|
global $dateTimeFormatter;
|
|
return $dateTimeFormatter->format(strtotime($row['StartDateTime']));
|
|
}),
|
|
# Left for backwards compatibility. Remove in 1.37
|
|
'EndDateTime' => true,
|
|
'EndDateTimeFormatted' => array('postFunction'=>function($row){
|
|
global $dateTimeFormatter;
|
|
return $dateTimeFormatter->format(strtotime($row['EndDateTime']));
|
|
}),
|
|
'Width' => true,
|
|
'Height' => true,
|
|
'Length' => true,
|
|
'Frames' => true,
|
|
'DefaultVideo' => true,
|
|
'AlarmFrames' => true,
|
|
'TotScore' => true,
|
|
'AvgScore' => true,
|
|
'MaxScore' => true,
|
|
'Archived' => true,
|
|
'Videoed' => true,
|
|
'Uploaded' => true,
|
|
'Emailed' => true,
|
|
'Messaged' => true,
|
|
'Executed' => true,
|
|
'Notes' => true,
|
|
'MinFrameId' => array( 'sql' => '(SELECT min(Frames.FrameId) FROM Frames WHERE EventId=Events.Id)' ),
|
|
'MaxFrameId' => array( 'sql' => '(SELECT max(Frames.FrameId) FROM Frames WHERE Events.Id = Frames.EventId)' ),
|
|
'MinFrameDelta' => array( 'sql' => '(SELECT min(Frames.Delta) FROM Frames WHERE Events.Id = Frames.EventId)' ),
|
|
'MaxFrameDelta' => array( 'sql' => '(SELECT max(Frames.Delta) FROM Frames WHERE Events.Id = Frames.EventId)' ),
|
|
),
|
|
),
|
|
'frames' => array(
|
|
'permission' => 'Events',
|
|
'table' => 'Frames',
|
|
'selector' => 'EventId',
|
|
'elements' => array(
|
|
'EventId' => true,
|
|
'FrameId' => true,
|
|
'Type' => true,
|
|
'Delta' => true,
|
|
// elements is a whitelist, so the event view's cue graph was reading an
|
|
// undefined Score off every frame until these were added.
|
|
'Score' => true,
|
|
'AudioLevel' => true,
|
|
),
|
|
),
|
|
'frame' => array(
|
|
'permission' => 'Events',
|
|
'table' => 'Frames',
|
|
'limit' => 1,
|
|
'selector' => array( array( 'table' => 'Events', 'join' => 'Events.Id = Frames.EventId', 'selector'=>'Events.Id' ), 'Frames.FrameId' ),
|
|
'elements' => array(
|
|
//'Id' => array( 'sql' => 'Frames.FrameId' ),
|
|
'FrameId' => true,
|
|
'EventId' => true,
|
|
'Type' => true,
|
|
'TimeStamp' => true,
|
|
'TimeStampShort' => array( 'sql' => 'date_format( StartDateTime, \''.MYSQL_FMT_DATETIME_SHORT.'\' )' ),
|
|
'Delta' => true,
|
|
'Score' => true,
|
|
//'Image' => array( 'postFunc' => 'getFrameImage' ),
|
|
),
|
|
),
|
|
'frameimage' => array(
|
|
'permission' => 'Events',
|
|
'func' => 'getFrameImage'
|
|
),
|
|
'nearframe' => array(
|
|
'permission' => 'Events',
|
|
'func' => 'getNearFrame'
|
|
),
|
|
'nearevents' => array(
|
|
'permission' => 'Events',
|
|
'func' => 'getNearEvents'
|
|
)
|
|
);
|
|
|
|
function collectData() {
|
|
global $statusData;
|
|
|
|
$entity = strtolower(validJsStr($_REQUEST['entity']));
|
|
$entitySpec = &$statusData[$entity];
|
|
#print_r( $entitySpec );
|
|
if (isset($entitySpec['permission'])) {
|
|
if (!canView($entitySpec['permission'])) {
|
|
ajaxError('Unrecognised action or insufficient permissions for '.$entity.' permission: '.$entitySpec['permission']);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( !empty($entitySpec['func']) ) {
|
|
return call_user_func($entitySpec['func']);
|
|
}
|
|
|
|
$data = array();
|
|
$postFuncs = array();
|
|
$postFunctions = array();
|
|
|
|
$fieldSql = array();
|
|
$joinSql = array();
|
|
$groupSql = array();
|
|
$values = array();
|
|
|
|
$elements = &$entitySpec['elements'];
|
|
$lc_elements = array_change_key_case($elements);
|
|
|
|
$id = false;
|
|
if ( isset($_REQUEST['id']) )
|
|
if ( !is_array($_REQUEST['id']) )
|
|
$id = array( validJsStr($_REQUEST['id']) );
|
|
else
|
|
$id = array_values($_REQUEST['id']);
|
|
|
|
# Early per-object permission check before processing elements.
|
|
# This must happen before the element loop because func/zmu elements
|
|
# execute immediately and would bypass the post-SQL canView() check.
|
|
if (isset($entitySpec['object']) && $id) {
|
|
$object_name = 'ZM\\'.$entitySpec['object'];
|
|
$object = new $object_name($id[0]);
|
|
if (!$object->Id()) {
|
|
ajaxError('Not found: '.$entity.' id '.$id[0]);
|
|
return;
|
|
}
|
|
if (!$object->canView()) {
|
|
ajaxError('Insufficient permissions for '.$entity.' id '.$id[0]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( !isset($_REQUEST['element']) )
|
|
$_REQUEST['element'] = array_keys($elements);
|
|
else if ( !is_array($_REQUEST['element']) )
|
|
$_REQUEST['element'] = array( validJsStr($_REQUEST['element']) );
|
|
|
|
if ( isset($entitySpec['selector']) ) {
|
|
if ( !is_array($entitySpec['selector']) )
|
|
$entitySpec['selector'] = array( $entitySpec['selector'] );
|
|
foreach( $entitySpec['selector'] as $selector )
|
|
if ( is_array( $selector ) && isset($selector['table']) && isset($selector['join']) )
|
|
$joinSql[] = 'left join '.$selector['table'].' on '.$selector['join'];
|
|
}
|
|
|
|
foreach ( $_REQUEST['element'] as $element ) {
|
|
if ( !($elementData = $lc_elements[strtolower($element)]) ) {
|
|
ajaxError('Bad '.validJsStr($_REQUEST['entity']).' element '.$element);
|
|
continue;
|
|
}
|
|
if (isset($elementData['func'])) {
|
|
$data[$element] = call_user_func($elementData['func']);
|
|
} else if ( isset($elementData['postFunc']) ) {
|
|
$postFuncs[$element] = $elementData['postFunc'];
|
|
} else if ( isset($elementData['postFunction']) ) {
|
|
$postFunctions[$element] = $elementData['postFunction'];
|
|
} else if ( isset($elementData['zmu']) ) {
|
|
$command = escapeshellcmd(getZmuCommand(' '.$elementData['zmu']));
|
|
$data[$element] = exec($command);
|
|
} else {
|
|
if ( isset($elementData['sql']) )
|
|
$fieldSql[] = $elementData['sql'].' as '.$element;
|
|
else
|
|
$fieldSql[] = '`'.$element.'`';
|
|
if ( isset($elementData['table']) && isset($elementData['join']) ) {
|
|
$joinSql[] = 'left join '.$elementData['table'].' on '.$elementData['join'];
|
|
}
|
|
if ( isset($elementData['group']) ) {
|
|
$groupSql[] = $elementData['group'];
|
|
}
|
|
}
|
|
} # end foreach element
|
|
|
|
if (isset($entitySpec['object'])) {
|
|
$fieldSql[] = 'Id';
|
|
}
|
|
|
|
if ( count($fieldSql) ) {
|
|
$sql = 'SELECT '.join(', ', $fieldSql).' FROM '.$entitySpec['table'];
|
|
if ( $joinSql )
|
|
$sql .= ' '.join(' ', array_unique($joinSql));
|
|
if ( $id && !empty($entitySpec['selector']) ) {
|
|
$index = 0;
|
|
$where = array();
|
|
foreach ( $entitySpec['selector'] as $selIndex => $selector ) {
|
|
$selectorParamName = ':selector' . $selIndex;
|
|
if ( is_array($selector) ) {
|
|
$where[] = $selector['selector'].' = '.$selectorParamName;
|
|
$values[$selectorParamName] = validInt($id[$index]);
|
|
} else {
|
|
$where[] = $selector.' = '.$selectorParamName;
|
|
$values[$selectorParamName] = validInt($id[$index]);
|
|
}
|
|
$index++;
|
|
}
|
|
$sql .= ' WHERE '.join(' AND ', $where);
|
|
}
|
|
if ( $groupSql )
|
|
$sql .= ' GROUP BY '.join(',', array_unique($groupSql));
|
|
if ( !empty($_REQUEST['sort']) ) {
|
|
$sort_fields = explode(',', $_REQUEST['sort']);
|
|
$order_clauses = array();
|
|
foreach ( $sort_fields as $sort_field ) {
|
|
if (!preg_match('/^`?(\w+)`?\s*(ASC|DESC)?( NULLS FIRST)?$/i', $sort_field, $matches)) {
|
|
ZM\Error('Sort field didn\'t match regexp '.$sort_field);
|
|
continue;
|
|
}
|
|
// Check that the field name is one we are actually selecting.
|
|
// $fieldSql entries may be bare (`Name`), backtick-wrapped (`Name`),
|
|
// or aliased (expression as Name), so match against all forms.
|
|
$field = $matches[1];
|
|
$found = false;
|
|
foreach ($fieldSql as $f) {
|
|
if ($f === $field || $f === '`'.$field.'`' || preg_match('/\bas\s+`?'.$field.'`?$/i', $f)) {
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!$found) {
|
|
ZM\Error('Sort field '.$field.' from '.$sort_field.' not in SQL Fields: '.join(',', $fieldSql));
|
|
continue;
|
|
}
|
|
$clause = '`'.$field.'`';
|
|
if (!empty($matches[2])) {
|
|
$clause .= ' '.strtoupper($matches[2]);
|
|
}
|
|
if (!empty($matches[3])) {
|
|
$clause .= ' '.strtoupper(trim($matches[3]));
|
|
}
|
|
$order_clauses[] = $clause;
|
|
} # end foreach sort field
|
|
if ($order_clauses) {
|
|
$sql .= ' ORDER BY '.join(', ', $order_clauses);
|
|
}
|
|
} # end if has sort
|
|
if ( !empty($entitySpec['limit']) )
|
|
$limit = $entitySpec['limit'];
|
|
elseif ( !empty($_REQUEST['count']) )
|
|
$limit = validInt($_REQUEST['count']);
|
|
$limit_offset = '';
|
|
if ( !empty($_REQUEST['offset']) )
|
|
$limit_offset = validInt($_REQUEST['offset']) . ', ';
|
|
if ( !empty($limit) )
|
|
$sql .= ' limit '.$limit_offset.$limit;
|
|
if ( isset($limit) && ($limit == 1) ) {
|
|
if ( $sqlData = dbFetchOne($sql, NULL, $values) ) {
|
|
if (isset($entitySpec['object'])) {
|
|
ZM\Debug("Have object".$entitySpec['object']);
|
|
$object_name = 'ZM\\'.$entitySpec['object'];
|
|
$object = new $object_name($sqlData);
|
|
ZM\Debug("Canview:".$object->canView());
|
|
if (!$object->canView()) {
|
|
ajaxError('Insufficient permissions for '.$entity.' id '.$sqlData['Id']);
|
|
return;
|
|
}
|
|
}
|
|
foreach ( $postFuncs as $element=>$func )
|
|
$sqlData[$element] = call_user_func($func, $sqlData);
|
|
foreach ( $postFunctions as $element=>$function )
|
|
$sqlData[$element] = $function($sqlData);
|
|
$data = array_merge($data, $sqlData);
|
|
}
|
|
} else {
|
|
$count = 0;
|
|
foreach ( dbFetchAll($sql, NULL, $values) as $sqlData ) {
|
|
|
|
if (isset($entitySpec['object'])) {
|
|
ZM\Debug("Have object".$entitySpec['object']);
|
|
$object_name = 'ZM\\'.$entitySpec['object'];
|
|
$object = new $object_name($sqlData);
|
|
ZM\Debug("Canview:".$object->canView());
|
|
if (!$object->canView()) continue;
|
|
}
|
|
|
|
foreach ( $postFuncs as $element=>$func )
|
|
$sqlData[$element] = call_user_func($func, $sqlData);
|
|
foreach ( $postFunctions as $element=>$function )
|
|
$sqlData[$element] = $function($sqlData);
|
|
$data[] = $sqlData;
|
|
if ( isset($limit) && ++$count >= $limit )
|
|
break;
|
|
} # end foreach
|
|
} # end if have limit == 1
|
|
} else {
|
|
ZM\Debug("No fieldSQL");
|
|
}
|
|
//ZM\Debug(print_r($data, true));
|
|
return $data;
|
|
}
|
|
|
|
function formatDateTime($dt) {
|
|
return $dateTimeFormatter->format(strtotime($dt));
|
|
}
|
|
|
|
$data = collectData();
|
|
|
|
if ( !isset($_REQUEST['layout']) ) {
|
|
$_REQUEST['layout'] = 'json';
|
|
}
|
|
|
|
switch ( $_REQUEST['layout'] ) {
|
|
case 'xml NOT CURRENTLY SUPPORTED' :
|
|
header('Content-type: application/xml');
|
|
echo('<?xml version="1.0" encoding="iso-8859-1"?>
|
|
');
|
|
$entity = strtolower($_REQUEST['entity']);
|
|
$entity = preg_replace('/[^A-Za-z0-9]/', '', $entity);
|
|
echo '<'.$entity.'>
|
|
';
|
|
foreach ( $data as $key=>$value ) {
|
|
$key = strtolower($key);
|
|
echo "<$key>".htmlentities($value)."</$key>\n";
|
|
}
|
|
echo '</'.$entity.">\n";
|
|
break;
|
|
case 'json' :
|
|
{
|
|
$response = array( strtolower(validJsStr($_REQUEST['entity'])) => $data );
|
|
if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') ) {
|
|
$auth_hash = generateAuthHash(ZM_AUTH_HASH_IPS);
|
|
$response['auth'] = $auth_hash;
|
|
$response['auth_relay'] = get_auth_relay();
|
|
}
|
|
if ( isset($_REQUEST['loopback']) )
|
|
$response['loopback'] = validJsStr($_REQUEST['loopback']);
|
|
#ZM\Warning(print_r($response, true));
|
|
ajaxResponse($response);
|
|
break;
|
|
}
|
|
case 'text' :
|
|
header('Content-type: text/plain');
|
|
echo join(' ', array_values($data));
|
|
break;
|
|
default:
|
|
ZM\Error('Unsupported layout: '.$_REQUEST['layout']);
|
|
}
|
|
|
|
function getFrameImage() {
|
|
$eventId = validCardinal($_REQUEST['eid']);
|
|
$frameId = validCardinal($_REQUEST['fid']);
|
|
|
|
$sql = 'SELECT * FROM Frames WHERE EventId = ? AND FrameId = ?';
|
|
if ( !($frame = dbFetchOne($sql, NULL, array($eventId, $frameId))) ) {
|
|
ZM\Debug("Frame not found for event $eventId frame $frameId");
|
|
$frame = array();
|
|
$frame['EventId'] = $eventId;
|
|
$frame['FrameId'] = $frameId;
|
|
$frame['Type'] = 'Virtual';
|
|
}
|
|
$event = new ZM\Event($frame['EventId']);
|
|
$frame['Image'] = $event->getImageSrc($frame, SCALE_BASE);
|
|
return $frame;
|
|
}
|
|
|
|
function getNearFrame() {
|
|
$eventId = validCardinal($_REQUEST['id'][0]);
|
|
$frameId = validCardinal($_REQUEST['id'][1]);
|
|
|
|
$sql = 'SELECT FrameId FROM Frames WHERE EventId = ? AND FrameId <= ? ORDER BY FrameId DESC LIMIT 1';
|
|
if ( !$nearFrameId = dbFetchOne($sql, 'FrameId', array($eventId, $frameId)) ) {
|
|
$sql = 'SELECT * FROM Frames WHERE EventId = ? AND FrameId > ? ORDER BY FrameId ASC LIMIT 1';
|
|
if ( !$nearFrameId = dbFetchOne($sql, 'FrameId', array($eventId, $frameId)) ) {
|
|
return( array() );
|
|
}
|
|
}
|
|
$_REQUEST['entity'] = 'frame';
|
|
$_REQUEST['id'][1] = $nearFrameId;
|
|
return collectData();
|
|
}
|
|
|
|
function getNearEvents() {
|
|
global $user, $sortColumn, $sortOrder;
|
|
|
|
$eventId = validCardinal($_REQUEST['id']);
|
|
$NearEvents = array('EventId'=>$eventId);
|
|
|
|
$event = dbFetchOne('SELECT * FROM Events WHERE Id=?', NULL, array($eventId));
|
|
if ( !$event ) return $NearEvents;
|
|
|
|
$filter = ZM\Filter::parse($_REQUEST['filter']);
|
|
parseSort();
|
|
if ( count($user->unviewableMonitorIds()) ) {
|
|
$filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user->viewableMonitorIds()));
|
|
}
|
|
$filter_sql = $filter->sql();
|
|
|
|
# When listing, it may make sense to list them in descending order.
|
|
# But when viewing Prev should be timewise earlier and Next should be after.
|
|
if ( $sortColumn == 'E.Id' or $sortColumn == 'E.StartDateTime' ) {
|
|
$sortOrder = 'ASC';
|
|
}
|
|
|
|
$sql = '
|
|
SELECT E.Id AS Id, E.StartDateTime AS StartDateTime
|
|
FROM Events AS E
|
|
INNER JOIN Monitors AS M ON E.MonitorId = M.Id
|
|
LEFT JOIN Events_Tags AS ET ON E.Id = ET.EventId
|
|
LEFT JOIN Tags AS T ON T.Id = ET.TagId
|
|
WHERE E.Id != ? AND '.$sortColumn.'
|
|
'.($sortOrder=='ASC'?'<=':'>=').' ?';
|
|
if ($filter->sql()) {
|
|
$sql .= ' AND ('.$filter->sql().')';
|
|
}
|
|
$sql .= ' AND E.StartDateTime <= ? ORDER BY '.$sortColumn.' '.($sortOrder=='ASC'?'DESC':'ASC');
|
|
if ( $sortColumn != 'E.Id' ) {
|
|
# When sorting by starttime, if we have two events with the same starttime (different monitors) then we should sort secondly by Id
|
|
$sql .= ', E.Id DESC';
|
|
}
|
|
$sql .= ' LIMIT 1';
|
|
$result = dbQuery($sql, [$eventId, $event[$_REQUEST['sort_field']], $event['StartDateTime']]);
|
|
if ( !$result ) {
|
|
ZM\Error('Failed to load previous event using '.$sql);
|
|
return $NearEvents;
|
|
}
|
|
|
|
$prevEvent = dbFetchNext($result);
|
|
|
|
$sql = '
|
|
SELECT E.Id AS Id, E.StartDateTime AS StartDateTime
|
|
FROM Events AS E
|
|
INNER JOIN Monitors AS M ON E.MonitorId = M.Id
|
|
LEFT JOIN Events_Tags AS ET ON E.Id = ET.EventId
|
|
LEFT JOIN Tags AS T ON T.Id = ET.TagId
|
|
WHERE E.Id != ? AND '.$sortColumn.'
|
|
'.($sortOrder=='ASC'?'>=':'<=').' ?';
|
|
if ($filter->sql()) {
|
|
$sql .= ' AND ('.$filter->sql().')';
|
|
}
|
|
$sql .= ' AND E.StartDateTime >= ? ORDER BY '.$sortColumn.' '.($sortOrder=='ASC'?'ASC':'DESC');
|
|
if ( $sortColumn != 'E.Id' ) {
|
|
# When sorting by starttime, if we have two events with the same starttime (different monitors) then we should sort secondly by Id
|
|
$sql .= ', E.Id ASC';
|
|
}
|
|
$sql .= ' LIMIT 1';
|
|
$result = dbQuery($sql, [$eventId, $event[$_REQUEST['sort_field']], $event['StartDateTime']]);
|
|
if ( !$result ) {
|
|
ZM\Error('Failed to load next event using '.$sql);
|
|
return $NearEvents;
|
|
}
|
|
$nextEvent = dbFetchNext($result);
|
|
|
|
if ( $prevEvent ) {
|
|
$NearEvents['PrevEventId'] = $prevEvent['Id'];
|
|
$NearEvents['PrevEventStartTime'] = $prevEvent['StartDateTime'];
|
|
$NearEvents['PrevEventDefVideoPath'] = getEventDefaultVideoPath($prevEvent['Id']);
|
|
} else {
|
|
$NearEvents['PrevEventId'] = $NearEvents['PrevEventStartTime'] = $NearEvents['PrevEventDefVideoPath'] = 0;
|
|
}
|
|
if ( $nextEvent ) {
|
|
$NearEvents['NextEventId'] = $nextEvent['Id'];
|
|
$NearEvents['NextEventStartTime'] = $nextEvent['StartDateTime'];
|
|
$NearEvents['NextEventDefVideoPath'] = getEventDefaultVideoPath($nextEvent['Id']);
|
|
} else {
|
|
$NearEvents['NextEventId'] = $NearEvents['NextEventStartTime'] = $NearEvents['NextEventDefVideoPath'] = 0;
|
|
}
|
|
return $NearEvents;
|
|
} # end function getNearEvents()
|
|
|
|
?>
|