mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-03-05 14:38:23 -05:00
* Allow API authentication using the `auth` query parameter containing an auth. hash. Fixes #1827 The same auth. hash for zms is used here. This allows consumers to use the API without sending the password in the query string and not require forging logins via the login form. * Move logger.php's global Debug function to Logger::Debug to avoid polluting globals This avoids a conflict with CakePHP when logger.php gets included indrectly from API code. * Protect action=login when ZM_ENABLE_CSRF_MAGIC is enabled
309 lines
11 KiB
PHP
309 lines
11 KiB
PHP
<?php
|
|
|
|
class Event {
|
|
public function __construct( $IdOrRow ) {
|
|
$row = NULL;
|
|
if ( $IdOrRow ) {
|
|
if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) {
|
|
$row = dbFetchOne( 'SELECT *,unix_timestamp(StartTime) as Time FROM Events WHERE Id=?', NULL, array( $IdOrRow ) );
|
|
if ( ! $row ) {
|
|
Error('Unable to load Event record for Id=' . $IdOrRow );
|
|
}
|
|
} elseif ( is_array( $IdOrRow ) ) {
|
|
$row = $IdOrRow;
|
|
} else {
|
|
$backTrace = debug_backtrace();
|
|
$file = $backTrace[1]['file'];
|
|
$line = $backTrace[1]['line'];
|
|
Error("Unknown argument passed to Event Constructor from $file:$line)");
|
|
Error("Unknown argument passed to Event Constructor ($IdOrRow)");
|
|
return;
|
|
}
|
|
} # end if isset($IdOrRow)
|
|
|
|
if ( $row ) {
|
|
foreach ($row as $k => $v) {
|
|
$this->{$k} = $v;
|
|
}
|
|
} else {
|
|
Error('No row for Event ' . $IdOrRow );
|
|
}
|
|
} // end function __construct
|
|
public function Storage() {
|
|
return new Storage( isset($this->{'StorageId'}) ? $this->{'StorageId'} : NULL );
|
|
}
|
|
public function Monitor() {
|
|
return new Monitor( isset($this->{'MonitorId'}) ? $this->{'MonitorId'} : NULL );
|
|
}
|
|
public function __call( $fn, array $args){
|
|
if ( array_key_exists( $fn, $this ) ) {
|
|
return $this->{$fn};
|
|
#array_unshift($args, $this);
|
|
#call_user_func_array( $this->{$fn}, $args);
|
|
}
|
|
}
|
|
|
|
public function Time() {
|
|
if ( ! isset( $this->{'Time'} ) ) {
|
|
$this->{'Time'} = strtotime($this->{'StartTime'});
|
|
}
|
|
return $this->{'Time'};
|
|
}
|
|
|
|
public function Path() {
|
|
$Storage = $this->Storage();
|
|
return $Storage->Path().'/'.$this->Relative_Path();
|
|
}
|
|
public function Relative_Path() {
|
|
$event_path = '';
|
|
|
|
if ( ZM_USE_DEEP_STORAGE ) {
|
|
$event_path = $this->{'MonitorId'} .'/'.strftime( '%y/%m/%d/%H/%M/%S', $this->Time()) ;
|
|
} else {
|
|
$event_path = $this->{'MonitorId'} .'/'.$this->{'Id'};
|
|
}
|
|
|
|
return( $event_path );
|
|
} // end function Relative_Path()
|
|
|
|
public function Link_Path() {
|
|
if ( ZM_USE_DEEP_STORAGE ) {
|
|
return $this->{'MonitorId'} .'/'.strftime( '%y/%m/%d/.', $this->Time()).$this->{'Id'};
|
|
}
|
|
Error('Calling Link_Path when not using deep storage');
|
|
return '';
|
|
}
|
|
|
|
public function delete() {
|
|
# This wouldn't work with foreign keys
|
|
dbQuery( 'DELETE FROM Events WHERE Id = ?', array($this->{'Id'}) );
|
|
if ( !ZM_OPT_FAST_DELETE ) {
|
|
dbQuery( 'DELETE FROM Stats WHERE EventId = ?', array($this->{'Id'}) );
|
|
dbQuery( 'DELETE FROM Frames WHERE EventId = ?', array($this->{'Id'}) );
|
|
if ( ZM_USE_DEEP_STORAGE ) {
|
|
|
|
# Assumption: All events have a start time
|
|
$start_date = date_parse( $this->{'StartTime'} );
|
|
if ( ! $start_date ) {
|
|
Error('Unable to parse start time for event ' . $this->{'Id'} . ' not deleting files.' );
|
|
return;
|
|
}
|
|
$start_date['year'] = $start_date['year'] % 100;
|
|
|
|
# So this is because ZM creates a link under the day pointing to the time that the event happened.
|
|
$link_path = $this->Link_Path();
|
|
if ( ! $link_path ) {
|
|
Error('Unable to determine link path for event ' . $this->{'Id'} . ' not deleting files.' );
|
|
return;
|
|
}
|
|
|
|
$Storage = $this->Storage();
|
|
$eventlink_path = $Storage->Path().'/'.$link_path;
|
|
|
|
if ( $id_files = glob( $eventlink_path ) ) {
|
|
if ( ! $eventPath = readlink($id_files[0]) ) {
|
|
Error("Unable to read link at $id_files[0]");
|
|
return;
|
|
}
|
|
# I know we are using arrays here, but really there can only ever be 1 in the array
|
|
$eventPath = preg_replace( '/\.'.$this->{'Id'}.'$/', $eventPath, $id_files[0] );
|
|
deletePath( $eventPath );
|
|
deletePath( $id_files[0] );
|
|
$pathParts = explode( '/', $eventPath );
|
|
for ( $i = count($pathParts)-1; $i >= 2; $i-- ) {
|
|
$deletePath = join( '/', array_slice( $pathParts, 0, $i ) );
|
|
if ( !glob( $deletePath."/*" ) ) {
|
|
deletePath( $deletePath );
|
|
}
|
|
}
|
|
} else {
|
|
Warning( "Found no event files under $eventlink_path" );
|
|
} # end if found files
|
|
} else {
|
|
$eventPath = $this->Path();
|
|
deletePath( $eventPath );
|
|
} # USE_DEEP_STORAGE OR NOT
|
|
} # ! ZM_OPT_FAST_DELETE
|
|
} # end Event->delete
|
|
|
|
public function getStreamSrc( $args, $querySep='&' ) {
|
|
return ( ZM_BASE_PATH != '/' ? ZM_BASE_PATH : '' ).'/index.php?view=view_video&eid='.$this->{'Id'};
|
|
|
|
$streamSrc = ZM_BASE_URL.ZM_PATH_ZMS;
|
|
|
|
$args[] = 'source=event&event='.$this->{'Id'};
|
|
|
|
if ( ZM_OPT_USE_AUTH ) {
|
|
if ( ZM_AUTH_RELAY == 'hashed' ) {
|
|
$args[] = 'auth='.generateAuthHash( ZM_AUTH_HASH_IPS );
|
|
} elseif ( ZM_AUTH_RELAY == 'plain' ) {
|
|
$args[] = 'user='.$_SESSION['username'];
|
|
$args[] = 'pass='.$_SESSION['password'];
|
|
} elseif ( ZM_AUTH_RELAY == "none" ) {
|
|
$args[] = 'user='.$_SESSION['username'];
|
|
}
|
|
}
|
|
if ( !in_array( 'mode=single', $args ) && !empty($GLOBALS['connkey']) ) {
|
|
$args[] = 'connkey='.$GLOBALS['connkey'];
|
|
}
|
|
if ( ZM_RAND_STREAM ) {
|
|
$args[] = 'rand='.time();
|
|
}
|
|
|
|
if ( count($args) ) {
|
|
$streamSrc .= '?'.join( $querySep, $args );
|
|
}
|
|
|
|
return( $streamSrc );
|
|
} // end function getStreamSrc
|
|
|
|
function DiskSpace() {
|
|
return folder_size( $this->Path() );
|
|
}
|
|
|
|
function createListThumbnail( $overwrite=false ) {
|
|
# Load the frame with the highest score to use as a thumbnail
|
|
if ( !($frame = dbFetchOne( 'SELECT * FROM Frames WHERE EventId=? AND Score=? ORDER BY FrameId LIMIT 1', NULL, array( $this->{'Id'}, $this->{'MaxScore'} ) )) ) {
|
|
Error("Unable to find a Frame matching max score " . $this->{'MaxScore'} . ' for event ' . $this->{'Id'} );
|
|
// FIXME: What if somehow the db frame was lost or score was changed? Should probably try another search for any frame.
|
|
return( false );
|
|
}
|
|
|
|
$frameId = $frame['FrameId'];
|
|
|
|
if ( ZM_WEB_LIST_THUMB_WIDTH ) {
|
|
$thumbWidth = ZM_WEB_LIST_THUMB_WIDTH;
|
|
$scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'Width'};
|
|
$thumbHeight = reScale( $this->{'Height'}, $scale );
|
|
} elseif ( ZM_WEB_LIST_THUMB_HEIGHT ) {
|
|
$thumbHeight = ZM_WEB_LIST_THUMB_HEIGHT;
|
|
$scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_HEIGHT)/$this->{'Height'};
|
|
$thumbWidth = reScale( $this->{'Width'}, $scale );
|
|
} else {
|
|
Fatal( "No thumbnail width or height specified, please check in Options->Web" );
|
|
}
|
|
|
|
$imageData = $this->getImageSrc( $frame, $scale, false, $overwrite );
|
|
if ( ! $imageData ) {
|
|
return ( false );
|
|
}
|
|
$thumbData = $frame;
|
|
$thumbData['Path'] = $imageData['thumbPath'];
|
|
$thumbData['Width'] = (int)$thumbWidth;
|
|
$thumbData['Height'] = (int)$thumbHeight;
|
|
|
|
return( $thumbData );
|
|
} // end function createListThumbnail
|
|
|
|
function getImageSrc( $frame, $scale=SCALE_BASE, $captureOnly=false, $overwrite=false ) {
|
|
$Storage = new Storage( isset($this->{'StorageId'}) ? $this->{'StorageId'} : NULL );
|
|
$Event = $this;
|
|
$eventPath = $Event->Path();
|
|
|
|
if ( !is_array($frame) )
|
|
$frame = array( 'FrameId'=>$frame, 'Type'=>'' );
|
|
|
|
if ( file_exists( $eventPath.'/snapshot.jpg' ) ) {
|
|
$captImage = "snapshot.jpg";
|
|
} else {
|
|
$captImage = sprintf( '%0'.ZM_EVENT_IMAGE_DIGITS.'d-capture.jpg', $frame['FrameId'] );
|
|
if ( ! file_exists( $eventPath.'/'.$captImage ) ) {
|
|
# Generate the frame JPG
|
|
if ( $Event->DefaultVideo() ) {
|
|
$videoPath = $eventPath.'/'.$Event->DefaultVideo();
|
|
|
|
if ( ! file_exists( $videoPath ) ) {
|
|
Error("Event claims to have a video file, but it does not seem to exist at $videoPath" );
|
|
return '';
|
|
}
|
|
|
|
$command ='ffmpeg -v 0 -i '.$videoPath.' -vf "select=gte(n\\,'.$frame['FrameId'].'),setpts=PTS-STARTPTS" '.$eventPath.'/'.$captImage;
|
|
Logger::Debug( "Running $command" );
|
|
$output = array();
|
|
$retval = 0;
|
|
exec( $command, $output, $retval );
|
|
Logger::Debug("Retval: $retval, output: " . implode("\n", $output));
|
|
} else {
|
|
Error("Can't create frame images from video becuase there is no video file for this event (".$Event->DefaultVideo() );
|
|
}
|
|
}
|
|
}
|
|
|
|
$captPath = $eventPath.'/'.$captImage;
|
|
if ( ! file_exists( $captPath ) ) {
|
|
Error( "Capture file does not exist at $captPath" );
|
|
return '';
|
|
}
|
|
$thumbCaptPath = ZM_DIR_IMAGES.'/'.$this->{'Id'}.'-'.$captImage;
|
|
|
|
//echo "CI:$captImage, CP:$captPath, TCP:$thumbCaptPath<br>";
|
|
|
|
$analImage = sprintf( '%0'.ZM_EVENT_IMAGE_DIGITS.'d-analyse.jpg', $frame['FrameId'] );
|
|
$analPath = $eventPath.'/'.$analImage;
|
|
|
|
$thumbAnalPath = ZM_DIR_IMAGES.'/'.$this->{'Id'}.'-'.$analImage;
|
|
//echo "AI:$analImage, AP:$analPath, TAP:$thumbAnalPath<br>";
|
|
|
|
$alarmFrame = $frame['Type']=='Alarm';
|
|
|
|
$hasAnalImage = $alarmFrame && file_exists( $analPath ) && filesize( $analPath );
|
|
$isAnalImage = $hasAnalImage && !$captureOnly;
|
|
|
|
if ( !ZM_WEB_SCALE_THUMBS || $scale >= SCALE_BASE || !function_exists( 'imagecreatefromjpeg' ) ) {
|
|
$imagePath = $thumbPath = $isAnalImage?$analPath:$captPath;
|
|
$imageFile = $imagePath;
|
|
$thumbFile = $thumbPath;
|
|
} else {
|
|
if ( version_compare( phpversion(), '4.3.10', '>=') )
|
|
$fraction = sprintf( '%.3F', $scale/SCALE_BASE );
|
|
else
|
|
$fraction = sprintf( '%.3f', $scale/SCALE_BASE );
|
|
$scale = (int)round( $scale );
|
|
|
|
$thumbCaptPath = preg_replace( '/\.jpg$/', "-$scale.jpg", $thumbCaptPath );
|
|
$thumbAnalPath = preg_replace( '/\.jpg$/', "-$scale.jpg", $thumbAnalPath );
|
|
|
|
if ( $isAnalImage ) {
|
|
$imagePath = $analPath;
|
|
$thumbPath = $thumbAnalPath;
|
|
} else {
|
|
$imagePath = $captPath;
|
|
$thumbPath = $thumbCaptPath;
|
|
}
|
|
|
|
$thumbFile = $thumbPath;
|
|
if ( $overwrite || !file_exists( $thumbFile ) || !filesize( $thumbFile ) )
|
|
{
|
|
// Get new dimensions
|
|
list( $imageWidth, $imageHeight ) = getimagesize( $imagePath );
|
|
$thumbWidth = $imageWidth * $fraction;
|
|
$thumbHeight = $imageHeight * $fraction;
|
|
|
|
// Resample
|
|
$thumbImage = imagecreatetruecolor( $thumbWidth, $thumbHeight );
|
|
$image = imagecreatefromjpeg( $imagePath );
|
|
imagecopyresampled( $thumbImage, $image, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $imageWidth, $imageHeight );
|
|
|
|
if ( !imagejpeg( $thumbImage, $thumbPath ) )
|
|
Error( "Can't create thumbnail '$thumbPath'" );
|
|
}
|
|
}
|
|
|
|
$imageData = array(
|
|
'eventPath' => $eventPath,
|
|
'imagePath' => $imagePath,
|
|
'thumbPath' => $thumbPath,
|
|
'imageFile' => $imagePath,
|
|
'thumbFile' => $thumbFile,
|
|
'imageClass' => $alarmFrame?"alarm":"normal",
|
|
'isAnalImage' => $isAnalImage,
|
|
'hasAnalImage' => $hasAnalImage,
|
|
);
|
|
|
|
return( $imageData );
|
|
}
|
|
|
|
} # end class
|
|
|
|
?>
|