mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-06 17:31:58 -04:00
getNearEvents compared only the sort column with >= / <= and excluded the current event, using Id solely as a secondary ORDER BY. When several events share a sort value - most often StartDateTime, which has one-second resolution, with multiple monitors recording in step - the later event of a tie chose the earlier one as its Next. Event playback, gapless or not, then looped between the two and the Next button could not escape. Compare the full (sort value, Id) tuple so ordering is strict in both directions. Read the current event's sort value through the same Events/Monitors join as the searches, which also makes Prev/Next work when sorting by MonitorName (the value was previously looked up in the Events row and was always NULL).
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';
|
|
}
|
|
|
|
# The sort value of the current event. Read through the same join as the searches so that
|
|
# sort columns from Monitors (M.Name) resolve too.
|
|
$sortValue = dbFetchOne('SELECT '.$sortColumn.' AS SortValue FROM Events AS E
|
|
INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE E.Id=?', 'SortValue', array($eventId));
|
|
|
|
# Events are ordered by ($sortColumn $sortOrder, E.Id ASC). Many events can share a sort value,
|
|
# e.g. several monitors starting events in the same second, so Prev/Next must compare the full
|
|
# (sort value, Id) tuple. Comparing the sort value alone with >= / <= lets the event after a tie
|
|
# pick the one before it as its Next, and playback loops between them forever.
|
|
$after = ($sortOrder == 'ASC') ? '>' : '<';
|
|
$before = ($sortOrder == 'ASC') ? '<' : '>';
|
|
$reverseOrder = ($sortOrder == 'ASC') ? 'DESC' : 'ASC';
|
|
|
|
$nearEvent = function($cmp, $idCmp, $startCmp, $order, $idOrder) use ($eventId, $event, $filter, $sortColumn, $sortValue) {
|
|
$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 ';
|
|
if ($sortColumn == 'E.Id') {
|
|
$sql .= 'E.Id '.$idCmp.' ?';
|
|
$values = [$eventId];
|
|
} else {
|
|
$sql .= '('.$sortColumn.' '.$cmp.' ? OR ('.$sortColumn.' = ? AND E.Id '.$idCmp.' ?))';
|
|
$values = [$sortValue, $sortValue, $eventId];
|
|
}
|
|
if ($filter->sql()) {
|
|
$sql .= ' AND ('.$filter->sql().')';
|
|
}
|
|
$sql .= ' AND E.StartDateTime '.$startCmp.' ?';
|
|
$values[] = $event['StartDateTime'];
|
|
$sql .= ' ORDER BY '.$sortColumn.' '.$order;
|
|
if ($sortColumn != 'E.Id') {
|
|
$sql .= ', E.Id '.$idOrder;
|
|
}
|
|
$sql .= ' LIMIT 1';
|
|
$result = dbQuery($sql, $values);
|
|
if (!$result) {
|
|
ZM\Error('Failed to load near event using '.$sql);
|
|
return false;
|
|
}
|
|
return dbFetchNext($result);
|
|
};
|
|
|
|
$prevEvent = $nearEvent($before, '<', '<=', $reverseOrder, 'DESC');
|
|
$nextEvent = $nearEvent($after, '>', '>=', $sortOrder, 'ASC');
|
|
|
|
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()
|
|
|
|
?>
|