translate('FnNone'), 'Monitor' => translate('FnMonitor'), 'Modect' => translate('FnModect'), 'Record' => translate('FnRecord'), 'Mocord' => translate('FnMocord'), 'Nodect' => translate('FnNodect') ); } return $FunctionTypes; } protected static $CapturingOptions = null; public static function getCapturingOptions() { if (!isset($CapturingOptions)) { $CapturingOptions = array( 'None'=>translate('None'), 'Ondemand' => translate('On Demand'), 'Always' => translate('Always'), ); } return $CapturingOptions; } protected static $AnalysingOptions = null; public static function getAnalysingOptions() { if (!isset($AnalysingOptions)) { $AnalysingOptions = array( 'None' => translate('None'), 'Always' => translate('Always'), ); } return $AnalysingOptions; } protected static $AnalysisSourceOptions = null; public static function getAnalysisSourceOptions() { if (!isset($AnalysisSourceOptions)) { $AnalysisSourceOptions = array( 'Primary' => translate('Primary'), 'Secondary' => translate('Secondary'), ); } return $AnalysisSourceOptions; } protected static $AnalysisImageOptions = null; public static function getAnalysisImageOptions() { if (!isset($AnalysisImageOptions)) { $AnalysisImageOptions = array( 'FullColour' => translate('Full Colour'), 'YChannel' => translate('Y-Channel (Greyscale)'), ); } return $AnalysisImageOptions; } protected static $RecordingOptions = null; public static function getRecordingOptions() { if (!isset($RecordingOptions)) { $RecordingOptions = array( 'None' => translate('None'), 'OnMotion' => translate('On Motion / Trigger / etc'), 'Always' => translate('Always'), ); } return $RecordingOptions; } protected static $RecordingSourceOptions = null; public static function getRecordingSourceOptions() { if (!isset($RecordingSourceOptions)) { $RecordingSourceOptions = array( 'Primary' => translate('Primary'), 'Secondary' => translate('Secondary'), 'Both' => translate('Both'), ); } return $RecordingSourceOptions; } protected static $DecodingOptions = null; public static function getDecodingOptions() { if (!isset($DecodingOptions)) { $DecodingOptions = array( 'None' => translate('None'), 'Ondemand' => translate('On Demand'), 'KeyFrames' => translate('KeyFrames Only'), 'KeyFrames+Ondemand' => translate('Keyframes + Ondemand'), 'Always' => translate('Always'), ); } return $DecodingOptions; } protected static $Statuses = null; public static function getStatuses() { if (!isset($Statuses)) { $Statuses = array( -1 => 'Unknown', 0 => 'Idle', 1 => 'PreAlarm', 2 => 'Alarm', 3 => 'Alert', 4 => 'Tape' ); } return $Statuses; } protected static $table = 'Monitors'; protected $defaults = array( 'Id' => null, 'Name' => array('type'=>'text','filter_regexp'=>'/[^\w\-\.\(\)\:\/ ]/', 'default'=>'Monitor'), 'Deleted' => 0, 'Notes' => '', 'ServerId' => 0, 'StorageId' => 0, 'ManufacturerId' => null, 'ModelId' => null, 'Type' => 'Ffmpeg', 'Capturing' => 'Always', 'Analysing' => 'Always', 'Recording' => 'Always', 'RecordingSource' => 'Primary', 'AnalysisSource' => 'Primary', 'AnalysisImage' => 'FullColour', 'Enabled' => array('type'=>'boolean','default'=>1), 'Decoding' => 'Always', 'RTSP2WebEnabled' => array('type'=>'integer','default'=>0), 'RTSP2WebType' => 'HLS', 'JanusEnabled' => array('type'=>'boolean','default'=>0), 'JanusAudioEnabled' => array('type'=>'boolean','default'=>0), 'Janus_Profile_Override' => '', 'Janus_Use_RTSP_Restream' => array('type'=>'boolean','default'=>0), 'Janus_RTSP_User' => null, 'Janus_RTSP_Session_Timeout' => array('type'=>'integer','default'=>0), 'LinkedMonitors' => array('type'=>'set', 'default'=>null), 'Triggers' => array('type'=>'set','default'=>''), 'EventStartCommand' => '', 'EventEndCommand' => '', 'ONVIF_URL' => '', 'ONVIF_Username' => '', 'ONVIF_Password' => '', 'ONVIF_Options' => '', 'ONVIF_Alarm_Text' => '', 'ONVIF_Event_Listener' => '0', 'use_Amcrest_API' => '0', 'Device' => '', 'Channel' => 0, 'Format' => '0', 'V4LMultiBuffer' => null, 'V4LCapturesPerFrame' => 1, 'Protocol' => null, 'Method' => '', 'Host' => null, 'Port' => '', 'SubPath' => '', 'Path' => null, 'SecondPath' => null, 'Options' => null, 'User' => null, 'Pass' => null, // These are NOT NULL default 0 in the db, but 0 is not a valid value. FIXME 'Width' => null, 'Height' => null, 'Colours' => 4, 'Palette' => '0', 'Orientation' => 'ROTATE_0', 'Deinterlacing' => 0, 'Decoder' => '', 'DecoderHWAccelName' => null, 'DecoderHWAccelDevice' => null, 'SaveJPEGs' => 0, 'VideoWriter' => '2', 'OutputCodec' => '0', 'Encoder' => 'auto', 'OutputContainer' => null, 'EncoderParameters' => '', 'RecordAudio' => array('type'=>'boolean', 'default'=>0), #'OutputSourceStream' => 'Primary', 'RTSPDescribe' => array('type'=>'boolean','default'=>0), 'Brightness' => -1, 'Contrast' => -1, 'Hue' => -1, 'Colour' => -1, 'EventPrefix' => 'Event-', 'LabelFormat' => '', 'LabelX' => 0, 'LabelY' => 0, 'LabelSize' => 2, 'ImageBufferCount' => 3, 'MaxImageBufferCount' => 121, 'WarmupCount' => 0, 'PreEventCount' => 5, 'PostEventCount' => 5, 'StreamReplayBuffer' => 0, 'AlarmFrameCount' => 1, 'SectionLength' => 600, 'SectionLengthWarn' => true, 'MinSectionLength' => 10, 'EventCloseMode' => 'system', 'FrameSkip' => 0, 'MotionFrameSkip' => 0, 'AnalysisFPSLimit' => 2, 'AnalysisUpdateDelay' => 0, 'MaxFPS' => null, 'AlarmMaxFPS' => null, 'FPSReportInterval' => 100, 'RefBlendPerc' => 6, 'AlarmRefBlendPerc' => 6, 'Controllable' => array('type'=>'boolean','default'=>0), 'ControlId' => null, 'ControlDevice' => null, 'ControlAddress' => null, 'AutoStopTimeout' => null, 'TrackMotion' => array('type'=>'boolean','default'=>0), 'TrackDelay' => null, 'ReturnLocation' => -1, 'ReturnDelay' => null, 'ModectDuringPTZ' => array('type'=>'boolean','default'=>0), 'DefaultRate' => 100, 'DefaultScale' => 0, 'SignalCheckPoints' => 0, 'SignalCheckColour' => '#0000BE', 'WebColour' => '#ff0000', 'Exif' => array('type'=>'boolean', 'default'=>0), 'Sequence' => null, 'ZoneCount' => 0, 'Refresh' => null, 'DefaultCodec' => 'auto', 'GroupIds' => array('default'=>array(), 'do_not_update'=>1), 'Latitude' => null, 'Longitude' => null, 'RTSPServer' => array('type'=>'boolean', 'default'=>0), 'RTSPStreamName' => '', 'SOAP_wsa_compl' => array('type'=>'boolean', 'default'=>0), 'Importance' => 'Normal', 'MQTT_Enabled' => array('type'=>'boolean','default'=>0), 'MQTT_Subscriptions' => '', ); private $status_fields = array( 'Status' => null, 'AnalysisFPS' => null, 'CaptureFPS' => null, 'CaptureBandwidth' => null, ); private $summary_fields = array( 'TotalEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'TotalEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'HourEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'HourEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'DayEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'DayEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'WeekEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'WeekEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'MonthEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'MonthEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'ArchivedEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'ArchivedEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), ); protected $Id; public function save($data = null) { if ($data) $this->set($data); if ($this->Manufacturer() and $this->Manufacturer()->Name() and ! $this->Manufacturer->Id()) { if ($this->Manufacturer()->save()) { $this->ManufacturerId = $this->Manufacturer()->Id(); } } if ($this->Model() and $this->Model()->Name() and ! $this->Model->Id()) { if ($this->Model()->save(['ManufacturerId'=>$this->ManufacturerId])) { $this->ModelId = $this->Model()->Id(); } } return parent::save(); } public function Janus_Pin() { if (!$this->{'JanusEnabled'}) return ''; if ((!defined('ZM_SERVER_ID')) or ( property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}) )) { $cmd = getZmuCommand(' --janus-pin -m '.$this->{'Id'}); $output = shell_exec($cmd); Debug("Running $cmd output: $output"); return $output ? trim($output) : $output; } else if ($this->ServerId()) { $result = $this->Server()->SendToApi('/monitors/'.$this->{'Id'}.'.json'); $json = json_decode($result, true); return ((isset($json['monitor']) and isset($json['monitor']['Monitor']) and isset($json['monitor']['Monitor']['Janus_Pin'])) ? $json['monitor']['Monitor']['Janus_Pin'] : ''); } else { Error('Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor.'); } } public function Control() { if (!property_exists($this, 'Control')) { if ($this->ControlId()) $this->{'Control'} = Control::find_one(array('Id'=>$this->{'ControlId'})); if (!(property_exists($this, 'Control') and $this->{'Control'})) $this->{'Control'} = new Control(); } return $this->{'Control'}; } public function Server() { if (!property_exists($this, 'Server')) { if ($this->ServerId()) { $this->{'Server'} = Server::find_one(array('Id'=>$this->{'ServerId'})); if (!$this->{'Server'}) { $this->{'Server'} = new Server(); } } if (!property_exists($this, 'Server')) { $this->{'Server'} = new Server(); } } return $this->{'Server'}; } public function Path($new=null) { // set the new value if requested if ($new !== null) { $this->{'Path'} = $new; } // empty value or old auth values terminate if (!isset($this->{'Path'}) or ($this->{'Path'}=='')) return ''; // extract the authentication part from the path given $values = extract_auth_values_from_url($this->{'Path'}); // If no values for User and Pass fields are present then terminate if (count($values) !== 2) { return $this->{'Path'}; } $old_us = isset($this->{'User'}) ? $this->{'User'} : ''; $old_ps = isset($this->{'Pass'}) ? $this->{'Pass'} : ''; $us = $values[0]; $ps = $values[1]; // Update the auth fields if they were empty and remove them from the path // or if they are equal between the path and field if ( (!$old_us && !$old_ps) || ($us == $old_us && $ps == $old_ps) ) { $this->{'Path'} = str_replace("$us:$ps@", '', $this->{'Path'}); $this->{'User'} = $us; $this->{'Pass'} = $ps; } return $this->{'Path'}; } public function User($new=null) { if ($new !== null) { // no url check if the update has different value $this->{'User'} = $new; } if (isset($this->{'User'}) and $this->{'User'} != '') return $this->{'User'}; // Only try to update from path if the field is empty $values = extract_auth_values_from_url($this->Path()); $this->{'User'} = count($values) == 2 ? $values[0] : ''; return $this->{'User'}; } public function Pass($new=null) { if ($new !== null) { // no url check if the update has different value $this->{'Pass'} = $new; } if (isset($this->{'Pass'}) and $this->{'Pass'} != '') return $this->{'Pass'}; // Only try to update from path if the field is empty $values = extract_auth_values_from_url($this->Path()); $this->{'Pass'} = count($values) == 2 ? $values[1] : ''; return $this->{'Pass'}; } public function __call($fn, array $args) { if (count($args)) { if (is_array($this->defaults[$fn]) and $this->defaults[$fn]['type'] == 'set') { $this->{$fn} = is_array($args[0]) ? implode(',', $args[0]) : $args[0]; } else { $this->{$fn} = $args[0]; } } if (property_exists($this, $fn)) { return $this->{$fn}; } else if (array_key_exists($fn, $this->defaults)) { if ( is_array($this->defaults[$fn]) ) { return $this->defaults[$fn]['default']; } return $this->defaults[$fn]; } else if (array_key_exists($fn, $this->status_fields)) { if ($this->Id()) { $sql = 'SELECT * FROM `Monitor_Status` WHERE `MonitorId`=?'; $row = dbFetchOne($sql, NULL, array($this->{'Id'})); if (!$row) { Warning('Unable to load Monitor status record for Id='.$this->{'Id'}.' using '.$sql); } else { foreach ($row as $k => $v) { $this->{$k} = $v; } return $this->{$fn}; } } # end if this->Id return null; } else if (array_key_exists($fn, $this->summary_fields)) { $sql = 'SELECT * FROM `Event_Summaries` WHERE `MonitorId`=?'; $row = dbFetchOne($sql, NULL, array($this->{'Id'})); if (!$row) { Warning('Unable to load Event Summary record for Id='.$this->{'Id'}.' using '.$sql); return null; } else { foreach ($row as $k => $v) { $this->{$k} = $v; } } return $this->{$fn}; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Warning("Unknown function call Monitor->$fn from $file:$line"); } } public function getStreamSrc($args, $querySep='&') { $streamSrc = $this->Server()->UrlToZMS( ZM_MIN_STREAMING_PORT ? ZM_MIN_STREAMING_PORT+$this->{'Id'} : null); $args['monitor'] = $this->{'Id'}; if (ZM_OPT_USE_AUTH) { if (ZM_AUTH_RELAY == 'hashed') { $args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS); # Include user so that db lookups can be more efficient $args['user'] = isset($_SESSION['username']) ? $_SESSION['username'] : ''; } elseif ( ZM_AUTH_RELAY == 'plain' ) { $args['user'] = isset($_SESSION['username']) ? $_SESSION['username'] : ''; $args['pass'] = isset($_SESSION['password']) ? $_SESSION['password'] : ''; } elseif ( ZM_AUTH_RELAY == 'none' ) { $args['user'] = $_SESSION['username']; } } if (ZM_RAND_STREAM) { $args['rand'] = time(); } # zms doesn't support width & height, so if no scale is set, default it if (!isset($args['scale'])) { if (isset($args['width']) and intval($args['width'])) { $args['scale'] = intval((100*intval($args['width']))/$this->ViewWidth()); } else if (isset($args['height']) and intval($args['height'])) { $args['scale'] = intval((100*intval($args['height']))/$this->ViewHeight()); } } if ($args['scale'] <= 0) { $args['scale'] = 100; } if (isset($args['width'])) unset($args['width']); if (isset($args['height'])) unset($args['height']); $streamSrc .= '?'.http_build_query($args, '', $querySep); return $streamSrc; } // end function getStreamSrc public function isPortrait() { return $this->ViewWidth() > $this->ViewHeight(); } public function isLandscape() { return $this->ViewWidth() < $this->ViewHeight(); } public function ViewWidth($new = null) { if ($new) $this->{'Width'} = $new; $field = ( $this->Orientation() == 'ROTATE_90' or $this->Orientation() == 'ROTATE_270' ) ? 'Height' : 'Width'; if (property_exists($this, $field)) return $this->{$field}; return $this->defaults[$field]; } // end function Width public function ViewHeight($new=null) { if ($new) $this->{'Height'} = $new; $field = ( $this->Orientation() == 'ROTATE_90' or $this->Orientation() == 'ROTATE_270' ) ? 'Width' : 'Height'; if (property_exists($this, $field)) return $this->{$field}; return $this->defaults[$field]; } // end function Height public function SignalCheckColour($new=null) { $field = 'SignalCheckColour'; if ($new) { $this->{$field} = $new; } // Validate that it's a valid colour (we seem to allow color names, not just hex). // This also helps prevent XSS. if (property_exists($this, $field) && preg_match('/^[#0-9a-zA-Z]+$/', $this->{$field})) { return $this->{$field}; } return $this->defaults[$field]; } // end function SignalCheckColour public static function find($parameters = array(), $options = array()) { return ZM_Object::_find(self::class, $parameters, $options); } public static function find_one($parameters = array(), $options = array()) { return ZM_Object::_find_one(self::class, $parameters, $options); } function zmcControl($mode=false) { if (!(property_exists($this,'Id') and $this->{'Id'})) { Warning('Attempt to control a monitor with no Id'); return; } if ((!defined('ZM_SERVER_ID')) or ( property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}) )) { if ($this->Type() == 'Local') { $zmcArgs = '-d '.$this->{'Device'}; } else { $zmcArgs = '-m '.$this->{'Id'}; } if ($mode == 'stop') { daemonControl('stop', 'zmc', $zmcArgs); } else if ($mode == 'reload') { daemonControl('reload', 'zmc', $zmcArgs); } else { if ($mode == 'restart') { daemonControl('stop', 'zmc', $zmcArgs); } if ($this->Capturing() != 'None') { daemonControl('start', 'zmc', $zmcArgs); } } } else if ($this->ServerId()) { $result = $this->Server()->SendToApi('/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zmc.json'); } else { Error('Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor.'); } } // end function zmcControl public function GroupIds($new='') { if ($new != '') { if (!is_array($new)) { $this->{'GroupIds'} = array($new); } else { $this->{'GroupIds'} = $new; } } if (!property_exists($this, 'GroupIds')) { if ($this->Id()) { $this->{'GroupIds'} = dbFetchAll('SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=?', 'GroupId', [$this->Id()]); if (!$this->{'GroupIds'}) $this->{'GroupIds'} = array(); } else { $this->{'GroupIds'} = array(); } } return $this->{'GroupIds'}; } public function delete() { $this->save(['Deleted'=>true]); } public function destroy() { $this->zmcControl('stop'); if (!$this->{'Id'}) { Warning('Attempt to destroy a monitor without id.'); return; } $this->zmcControl('stop'); // If fast deletes are on, then zmaudit will clean everything else up later // If fast deletes are off and there are lots of events then this step may // well time out before completing, in which case zmaudit will still tidy up if (!ZM_OPT_FAST_DELETE) { $markEids = dbFetchAll('SELECT Id FROM Events WHERE MonitorId=?', 'Id', array($this->{'Id'})); foreach ($markEids as $markEid) deleteEvent($markEid); if ($this->{'Name'}) deletePath(ZM_DIR_EVENTS.'/'.basename($this->{'Name'})); deletePath(ZM_DIR_EVENTS.'/'.$this->{'Id'}); $Storage = $this->Storage(); if ($Storage->Path() != ZM_DIR_EVENTS) { if ($this->{'Name'}) deletePath($Storage->Path().'/'.basename($this->{'Name'})); deletePath($Storage->Path().'/'.$this->{'Id'}); } } // end if !ZM_OPT_FAST_DELETE // This is the important stuff dbQuery('DELETE FROM Zones WHERE MonitorId = ?', array($this->{'Id'})); if ( ZM_OPT_X10 ) dbQuery('DELETE FROM TriggersX10 WHERE MonitorId=?', array($this->{'Id'})); dbQuery('DELETE FROM Monitor_Status WHERE MonitorId = ?', array($this->{'Id'})); dbQuery('DELETE FROM Event_Summaries WHERE MonitorId = ?', array($this->{'Id'})); dbQuery('DELETE FROM Monitors WHERE Id = ?', array($this->{'Id'})); } // end function destroy public function Storage($new = null) { if ($new) { $this->{'Storage'} = $new; } if (!(property_exists($this, 'Storage') and $this->{'Storage'})) { $this->{'Storage'} = isset($this->{'StorageId'}) ? Storage::find_one(array('Id'=>$this->{'StorageId'})) : new Storage(NULL); if (!$this->{'Storage'}) $this->{'Storage'} = new Storage(NULL); } return $this->{'Storage'}; } public function Source( ) { $source = ''; if ($this->{'Type'} == 'Local') { $source = $this->{'Device'}.' ('.$this->{'Channel'}.')'; } else if ($this->{'Type'} == 'Remote') { $source = preg_replace('/^.*@/', '', $this->{'Host'}); if ($this->{'Port'} != '80' and $this->{'Port'} != '554') { $source .= ':'.$this->{'Port'}; } } else if ($this->{'Type'} == 'VNC') { $source = preg_replace( '/^.*@/', '', $this->{'Host'} ); if ($this->{'Port'} != '5900') { $source .= ':'.$this->{'Port'}; } } else if ($this->{'Type'} == 'Ffmpeg' || $this->{'Type'} == 'Libvlc' || $this->{'Type'} == 'WebSite') { $url_parts = parse_url($this->{'Path'}); if (ZM_WEB_FILTER_SOURCE == 'Hostname') { # Filter out everything but the hostname if (isset($url_parts['host'])) { $source = $url_parts['host']; } else { $source = $this->{'Path'}; } } else if (ZM_WEB_FILTER_SOURCE == 'NoCredentials') { # Filter out sensitive and common items unset($url_parts['user']); unset($url_parts['pass']); #unset($url_parts['scheme']); unset($url_parts['query']); #unset($url_parts['path']); if (isset($url_parts['port']) and ($url_parts['port'] == '80' or $url_parts['port'] == '554')) unset($url_parts['port']); $source = unparse_url($url_parts); } else { # Don't filter anything $source = $this->{'Path'}; } } if ($source == '') { $source = 'Monitor ' . $this->{'Id'}; } return $source; } // end function Source public function UrlToIndex($port=null) { return $this->Server()->UrlToIndex($port); } public function UrlToZMS($port=null) { return $this->Server()->UrlToZMS($port).'?mid='.$this->Id(); } public function sendControlCommand($command) { // command is generally a command option list like --command=blah but might be just the word quit $options = array(); # Convert from a command line params to an option array foreach (explode(' ', $command) as $option) { if (preg_match('/--([^=]+)(?:=(.+))?/', $option, $matches)) { $options[$matches[1]] = isset($matches[2]) ? $matches[2] : 1; } else if ($option != '' and $option != 'quit' and $option != 'start' and $option != 'stop') { Warning("Ignored command for zmcontrol $option in $command"); } } if (!count($options)) { if ($command == 'quit' or $command == 'start' or $command == 'stop') { # These are special as we now run zmcontrol as a daemon through zmdc. $status = daemonStatus('zmcontrol.pl', array('--id', $this->{'Id'})); Debug("Current status $status"); if ($status or ((!defined('ZM_SERVER_ID')) or (property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'})))) { daemonControl($command, 'zmcontrol.pl', '--id '.$this->{'Id'}); return; } $options['command'] = $command; } else { Warning('No commands to send to zmcontrol from '.$command); return false; } } if ((!defined('ZM_SERVER_ID')) or (property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}))) { # Local Debug('Trying to send options ' . print_r($options, true)); $optionString = jsonEncode($options); Debug('Trying to send options '.$optionString); // Either connects to running zmcontrol.pl or runs zmcontrol.pl to send the command. $socket = socket_create(AF_UNIX, SOCK_STREAM, 0); if ($socket < 0) { Error('socket_create() failed: '.socket_strerror($socket)); return false; } $sockFile = ZM_PATH_SOCKS.'/zmcontrol-'.$this->{'Id'}.'.sock'; if (@socket_connect($socket, $sockFile)) { if (!socket_write($socket, $optionString)) { Error('Can\'t write to control socket: '.socket_strerror(socket_last_error($socket))); return false; } } else if ($command != 'quit') { $command = ZM_PATH_BIN.'/zmcontrol.pl '.$command.' --id '.$this->{'Id'}; // Can't connect so use script $ctrlOutput = exec(escapeshellcmd($command)); } socket_close($socket); } else if ($this->ServerId()) { $result = $this->Server()->SendToApi('/monitors/daemonControl/'.$this->{'Id'}.'/'.$command.'/zmcontrol.pl.json'); return $result; } else { Error('Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor.'); return false; } // end if we are on the recording server return true; } // end function sendControlCommand($mid, $command) function Groups($new='') { if ($new != '') $this->Groups = $new; if (!property_exists($this, 'Groups')) { $this->Groups = Group::find(array('Id'=>$this->GroupIds())); } return $this->Groups; } function connKey($new='') { if ($new) $this->connKey = $new; if (!isset($this->connKey)) { if (!empty($GLOBALS['connkey'])) { $this->connKey = $GLOBALS['connkey']; } else { $this->connKey = generateConnKey(); } } return $this->connKey; } function canEdit($u=null) { global $user; if ($u===null or $u->Id() == $user->Id()) return editableMonitor($this->{'Id'}); $monitor_permission = $u->Monitor_Permission($this->{'Id'}); if ($monitor_permission and ($monitor_permission->Permission() == 'None' or $monitor_permission->Permission() == 'View')) { Debug("Can't edit monitor ".$this->{'Id'}." because of monitor permission ".$monitor_permission->Permission()); return false; } $group_permissions = $u->Group_Permissions(); # If denied view in any group, then can't view it. foreach ($group_permissions as $permission) { if (!$permission->canEditMonitor($this->{'Id'})) { Debug("Can't edit monitor ".$this->{'Id'}." because of group ".$permision->Group()->Name().' '.$permision->Permission()); return false; } } return ($u['Monitors'] == 'Edit'); } function canView($u=null) { global $user; if (($u === null) or ($u->Id() == $user->Id())) return visibleMonitor($this->Id()); $monitor_permission = $u->Monitor_Permission($this->{'Id'}); if ($monitor_permission and ($monitor_permission->Permission() == 'None')) { Debug('Can\'t view monitor '.$this->{'Id'}.' because of monitor permission '.$monitor_permission->Permission()); return false; } $group_permissions = $u->Group_Permissions(); # If denied view in any group, then can't view it. $group_permission_value = 'Inherit'; foreach ($group_permissions as $permission) { $value = $permission->MonitorPermission($this->Id()); if ($value == 'None') { Debug('Can\'t view monitor '.$this->{'Id'}.' because of group '.$permission->Group()->Name().' '.$permission->Permission()); return false; } if ($value == 'Edit' or $value == 'View') { $group_permission_value = $value; } } if ($group_permission_value != 'Inherit') return true; return ($u->Monitors() != 'None'); } # end function canView function AlarmCommand($cmd) { if ((!defined('ZM_SERVER_ID')) or (property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}))) { switch ($cmd) { case 'on' : $cmd = ' -a'; break; case 'off': $cmd = ' -c'; break; case 'disable': $cmd = ' -n'; break; case 'status': $cmd = ' -s'; break; default: Warning("Invalid command $cmd in AlarmCommand"); return false; } $cmd = getZmuCommand($cmd.' -m '.validCardinal($this->{'Id'})); $output = shell_exec($cmd); Debug("Running $cmd output: $output"); return $output; } if ($this->ServerId()) { $result = $this->Server()->SendToApi('/monitors/alarm/id:'.$this->{'Id'}.'/command:'.$cmd.'.json'); if ($result === FALSE) { /* Handle error */ Error('Error sending command using '.$url); return false; } $json = json_decode($result, true); return $json['status']; } // end if we are on the recording server Error('Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor.'); return false; } function TriggerOn() { $output = $this->AlarmCommand('on'); if ($output and preg_match('/Alarmed event id: (\d+)$/', $output, $matches)) { return $matches[1]; } Warning('No event returned from TriggerOn'); } function TriggerOff() { $output = $this->AlarmCommand('off'); } function DisableAlarms() { $output = $this->AlarmCommand('disable'); } function Model($new=-1) { if ($new != -1) { Debug("New model $new"); $model = Model::find_one(['Name'=>$new]); if (!$model) { $model = new Model(); $model->set(['Name'=>$new, 'ManufacturerId'=>$this->ManufacturerId()]); $this->Model = $model; if ($this->ModelId) $this->ModelId = null; Debug("model: " . $model->Name() . ' ' . $model->Id() . ' ' . $this->ModelId()); } else { $this->ModelId = $model->Id(); Debug("Foud model: " . $model->Name() . ' ' . $model->Id() . ' ' . $this->ModelId()); } } if (!property_exists($this, 'Model')) { if (property_exists($this, 'ModelId') and $this->{'ModelId'}) { $this->{'Model'} = Model::find_one(array('Id'=>$this->ModelId())); if (!$this->{'Model'}) $this->{'Model'} = new Model(); } else { $this->{'Model'} = new Model(); } } return $this->{'Model'}; } function Manufacturer($new=-1) { if ($new != -1) { $manufacturer = Manufacturer::find_one(array('Name'=>$new)); if (!$manufacturer) { $manufacturer = new Manufacturer(); $manufacturer->set(['Name'=>$new]); $this->Manufacturer = $manufacturer; if ($this->ManufacturerId) $this->ManufacturerId = null; } else { $this->ManufacturerId = $manufacturer->Id(); } } if (!property_exists($this, 'Manufacturer')) { if (property_exists($this, 'ManufacturerId') and $this->{'ManufacturerId'}) { $this->{'Manufacturer'} = Manufacturer::find_one(array('Id'=>$this->ManufacturerId())); if (!$this->{'Manufacturer'}) $this->{'Manufacturer'} = new Manufacturer(); } else { $this->{'Manufacturer'} = new Manufacturer(); } } return $this->{'Manufacturer'}; } function getMonitorStateHTML() { $html = '