zoneminder/web/includes/Filter.php

705 lines
22 KiB
PHP

<?php
namespace ZM;
require_once('Object.php');
require_once('FilterTerm.php');
class Filter extends ZM_Object {
protected static $table = 'Filters';
protected $defaults = array(
'Id' => null,
'Name' => '',
'UserId' => 0,
'ExecuteInterval' => 60,
'AutoExecute' => 0,
'AutoExecuteCmd' => '',
'AutoEmail' => 0,
'EmailTo' => '',
'EmailSubject' => '',
'EmailBody' => '',
'AutoDelete' => 0,
'AutoArchive' => 0,
'AutoUnarchive' => 0,
'AutoVideo' => 0,
'AutoUpload' => 0,
'AutoMessage' => 0,
'AutoMove' => 0,
'AutoMoveTo' => 0,
'AutoCopy' => 0,
'AutoCopyTo' => 0,
'UpdateDiskSpace' => 0,
'Background' => 0,
'Concurrent' => 0,
'Query_json' => '',
'LockRows' => 0,
);
protected $_querystring;
protected $_sql;
protected $_hidden_fields;
public $_pre_sql_conditions;
public $_post_sql_conditions;
protected $_Terms;
public function sql() {
if (!isset($this->_sql)) {
$this->_sql = '';
foreach ( $this->FilterTerms() as $term ) {
#if ( ! ($term->is_pre_sql() or $term->is_post_sql()) ) {
$this->_sql .= $term->sql();
#} else {
#$this->_sql .= '1';
#}
} # end foreach term
}
return $this->_sql;
}
public function querystring($objectname='filter', $separator='&amp;') {
if ( (! isset($this->_querystring)) or ( $separator != '&amp;' ) or ($objectname != 'filter') ) {
$this->_querystring = '';
foreach ( $this->FilterTerms() as $term ) {
$this->_querystring .= $term->querystring($objectname, $separator);
} # end foreach term
$this->_querystring .= $separator.urlencode($objectname.'[Query][sort_asc]').'='.$this->sort_asc();
$this->_querystring .= $separator.urlencode($objectname.'[Query][sort_field]').'='.$this->sort_field();
$this->_querystring .= $separator.urlencode($objectname.'[Query][skip_locked]').'='.$this->skip_locked();
$this->_querystring .= $separator.urlencode($objectname.'[Query][limit]').'='.$this->limit();
if ( $this->Id() ) {
$this->_querystring .= $separator.$objectname.urlencode('[Id]').'='.$this->Id();
}
}
return $this->_querystring;
}
public function hidden_fields() {
if ( ! isset($this->_hidden_fields) ) {
$this->_hidden_fields = '';
foreach ( $this->FilterTerms() as $term ) {
$this->_hidden_fields .= $term->hidden_fields();
} # end foreach term
}
return $this->_hidden_fields;
}
public function pre_sql_conditions() {
if ( ! isset($this->_pre_sql_conditions) ) {
$this->_pre_sql_conditions = array();
foreach ( $this->FilterTerms() as $term ) {
if ( $term->is_pre_sql() )
$this->_pre_sql_conditions[] = $term;
} # end foreach term
}
return $this->_pre_sql_conditions;
}
public function post_sql_conditions() {
if ( ! isset($this->_post_sql_conditions) ) {
$this->_post_sql_conditions = array();
foreach ( $this->FilterTerms() as $term ) {
if ( $term->is_post_sql() )
$this->_post_sql_conditions[] = $term;
} # end foreach term
}
return $this->_post_sql_conditions;
}
public function FilterTerms() {
if ( ! isset($this->Terms) ) {
$this->Terms = array();
$_terms = $this->terms();
for ( $i = 0; $i < count($_terms); $i++ ) {
$term = new FilterTerm($this, $_terms[$i], $i);
$this->Terms[] = $term;
} # end foreach term
}
return $this->Terms;
}
public static function parse($new_filter, $querySep='&amp;') {
$filter = new Filter();
$filter->Query($new_filter['Query']);
return $filter;
}
# If no storage areas are specified in the terms, then return all
public function get_StorageAreas() {
$storage_ids = array();
foreach ( $this->Terms as $term ) {
if ( $term->attr == 'StorageId' ) {
# TODO handle other operators like !=
$storage_ids[] = $term->val;
}
}
if ( count($storage_ids) ) {
return Storage::find(array('Id'=>$storage_ids));
} else {
return Storage::find();
}
} # end function get_StorageAreas
public function Query_json() {
if ( func_num_args( ) ) {
$this->{'Query_json'} = func_get_arg(0);
$this->{'Query'} = jsonDecode($this->{'Query_json'});
}
return $this->{'Query_json'};
}
public function Query() {
if ( func_num_args( ) ) {
$this->{'Query'} = func_get_arg(0);
$this->{'Query_json'} = jsonEncode($this->{'Query'});
# We have altered the query so need to reset all the calculated results.
unset($this->_querystring);
unset($this->_sql);
unset($this->_hidden_fields);
unset($this->_pre_sql_conditions);
unset($this->_post_sql_conditions);
unset($this->_Terms);
}
if ( !property_exists($this, 'Query') ) {
if ( property_exists($this, 'Query_json') and $this->{'Query_json'} ) {
$this->{'Query'} = jsonDecode($this->{'Query_json'});
} else {
$this->{'Query'} = array();
}
} else {
if ( !is_array($this->{'Query'}) ) {
# Handle existence of both Query_json and Query in the row
$this->{'Query'} = jsonDecode($this->{'Query_json'});
}
}
return $this->{'Query'};
}
public static function find( $parameters = array(), $options = array() ) {
return ZM_Object::_find(get_class(), $parameters, $options);
}
public static function find_one( $parameters = array(), $options = array() ) {
return ZM_Object::_find_one(get_class(), $parameters, $options);
}
public function terms( ) {
if ( func_num_args() ) {
$Query = $this->Query();
$Query['terms'] = func_get_arg(0);
$this->Query($Query);
}
if ( isset( $this->Query()['terms'] ) ) {
return $this->Query()['terms'];
}
return array();
}
// The following three fields are actually stored in the Query
public function sort_field( ) {
if (func_num_args()) {
$Query = $this->Query();
$Query['sort_field'] = func_get_arg(0);
$this->Query($Query);
}
if (isset($this->Query()['sort_field'])) {
return $this->{'Query'}['sort_field'];
}
return ZM_WEB_EVENT_SORT_FIELD;
#return $this->defaults{'sort_field'};
}
public function sort_asc( ) {
if (func_num_args()) {
$Query = $this->Query();
$Query['sort_asc'] = func_get_arg(0);
$this->Query($Query);
}
if (isset($this->Query()['sort_asc'])) {
return $this->{'Query'}['sort_asc'];
}
return ZM_WEB_EVENT_SORT_ORDER == 'asc' ? 1 : 0;
#return $this->defaults{'sort_asc'};
}
public function skip_locked() {
if (func_num_args()) {
$Query = $this->Query();
$Query['skip_locked'] = func_get_arg(0);
$this->Query($Query);
}
if (isset($this->Query()['skip_locked']))
return $this->{'Query'}['skip_locked'];
return false;
}
public function limit( ) {
if ( func_num_args( ) ) {
$Query = $this->Query();
$Query['limit'] = func_get_arg(0);
$this->Query($Query);
}
if ( isset( $this->Query()['limit'] ) )
return $this->{'Query'}['limit'];
return 0;
#return $this->defaults{'limit'};
}
public function control($command, $server_id=null) {
$Servers = $server_id ? Server::find(array('Id'=>$server_id)) : Server::find(array('Status'=>'Running'));
if ( !count($Servers) ) {
if ( !$server_id ) {
# This will be the non-multi-server case
$Servers = array(new Server());
} else {
Warning("Server not found for id $server_id");
}
}
foreach ( $Servers as $Server ) {
if ( (!defined('ZM_SERVER_ID')) or (!$Server->Id()) or (ZM_SERVER_ID==$Server->Id()) ) {
# Local
Debug("Controlling filter locally $command for server ".$Server->Id());
daemonControl($command, 'zmfilter.pl', '--filter_id='.$this->{'Id'}.' --daemon');
} else {
# Remote case
$url = $Server->UrlToIndex();
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS);
} else if ( ZM_AUTH_RELAY == 'plain' ) {
$url = '?user='.$_SESSION['username'];
$url = '?pass='.$_SESSION['password'];
} else if ( ZM_AUTH_RELAY == 'none' ) {
$url = '?user='.$_SESSION['username'];
}
}
$url .= '&view=filter&object=filter&action=control&command='.$command.'&Id='.$this->Id().'&ServerId='.$Server->Id();
Debug("sending command to $url");
$data = array();
if ( defined('ZM_ENABLE_CSRF_MAGIC') ) {
require_once( 'includes/csrf/csrf-magic.php' );
$data['__csrf_magic'] = csrf_get_tokens();
}
// use key 'http' even if you send the request to https://...
$options = array(
'http' => array(
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data)
)
);
$context = stream_context_create($options);
try {
$result = file_get_contents($url, false, $context);
if ( $result === FALSE ) { /* Handle error */
Error("Error restarting zmfilter.pl using $url");
}
} catch ( Exception $e ) {
Error("Except $e thrown trying to restart zmfilter");
}
} # end if local or remote
} # end foreach erver
} # end function control
public function execute() {
$command = ZM_PATH_BIN.'/zmfilter.pl --filter_id='.escapeshellarg($this->Id());
$result = exec($command, $output, $status);
Debug("$command status:$status output:".implode("\n", $output));
return $status;
}
public function test_pre_sql_conditions() {
if (count($this->pre_sql_conditions())) {
foreach ($this->pre_sql_conditions() as $term) {
if (!$term->test()) return false;
}
} # end if pre_sql_conditions
return true;
}
public function test_post_sql_conditions($event) {
if (count($this->post_sql_conditions())) {
foreach ($this->post_sql_conditions() as $term) {
if (!$term->test($event)) return false;
}
} # end if pre_sql_conditions
return true;
}
function tree() {
$terms = $this->terms();
if ( count($terms) <= 0 ) {
return false;
}
$StorageArea = NULL;
$postfixExpr = array();
$postfixStack = array();
$priorities = array(
'<' => 1,
'<=' => 1,
'>' => 1,
'>=' => 1,
'=' => 2,
'!=' => 2,
'=~' => 2,
'!~' => 2,
'=[]' => 2,
'![]' => 2,
'and' => 3,
'or' => 4,
'IS' => 2,
'IS NOT' => 2,
);
for ( $i = 0; $i < count($terms); $i++ ) {
$term = $terms[$i];
if ( !empty($term['cnj']) ) {
while ( true ) {
if ( !count($postfixStack) ) {
$postfixStack[] = array('type'=>'cnj', 'value'=>$term['cnj'], 'sqlValue'=>$term['cnj']);
break;
} elseif ( $postfixStack[count($postfixStack)-1]['type'] == 'obr' ) {
$postfixStack[] = array('type'=>'cnj', 'value'=>$term['cnj'], 'sqlValue'=>$term['cnj']);
break;
} elseif ( $priorities[$term['cnj']] < $priorities[$postfixStack[count($postfixStack)-1]['value']] ) {
$postfixStack[] = array('type'=>'cnj', 'value'=>$term['cnj'], 'sqlValue'=>$term['cnj']);
break;
} else {
$postfixExpr[] = array_pop($postfixStack);
}
}
} # end if ! empty cnj
if ( !empty($term['obr']) ) {
for ( $j = 0; $j < $term['obr']; $j++ ) {
$postfixStack[] = array('type'=>'obr', 'value'=>$term['obr']);
}
}
if ( !empty($term['attr']) ) {
$dtAttr = false;
switch ( $term['attr']) {
case 'MonitorName':
$sqlValue = 'M.'.preg_replace( '/^Monitor/', '', $term['attr']);
break;
case 'ServerId':
$sqlValue .= 'M.ServerId';
break;
case 'StorageServerId':
$sqlValue .= 'S.ServerId';
break;
case 'FilterServerId':
$sqlValue .= ZM_SERVER_ID;
break;
case 'DateTime':
case 'StartDateTime':
$sqlValue = 'E.StartDateTime';
$dtAttr = true;
break;
case 'Date':
case 'StartDate':
$sqlValue = 'to_days(E.StartDateTime)';
$dtAttr = true;
break;
case 'Time':
case 'StartTime':
$sqlValue = 'extract(hour_second from E.StartDateTime)';
break;
case 'Weekday':
case 'StartWeekday':
$sqlValue = 'weekday(E.StartDateTime)';
break;
case 'EndDateTime':
$sqlValue = 'E.EndDateTime';
$dtAttr = true;
break;
case 'EndDate':
$sqlValue = 'to_days(E.EndDateTime)';
$dtAttr = true;
break;
case 'EndTime':
$sqlValue = 'extract(hour_second from E.EndDateTime)';
break;
case 'EndWeekday':
$sqlValue = 'weekday(E.EndDateTime)';
break;
case 'Id':
case 'Name':
case 'MonitorId':
case 'StorageId':
case 'SecondaryStorageId':
case 'Length':
case 'Frames':
case 'AlarmFrames':
case 'TotScore':
case 'AvgScore':
case 'MaxScore':
case 'Cause':
case 'Notes':
case 'StateId':
case 'Archived':
$sqlValue = 'E.'.$term['attr'];
break;
case 'DiskPercent':
// Need to specify a storage area, so need to look through other terms looking for a storage area, else we default to ZM_EVENTS_PATH
if ( ! $StorageArea ) {
for ( $j = 0; $j < count($terms); $j++ ) {
if ( isset($terms[$j]['attr']) and $terms[$j]['attr'] == 'StorageId' and isset($terms[$j]['val']) ) {
$StorageArea = new Storage($terms[$j]['val']);
break;
}
} // end foreach remaining term
if ( ! $StorageArea ) $StorageArea = new Storage();
} // end no StorageArea found yet
$sqlValue = getDiskPercent($StorageArea);
break;
case 'DiskBlocks':
// Need to specify a storage area, so need to look through other terms looking for a storage area, else we default to ZM_EVENTS_PATH
if ( ! $StorageArea ) {
for ( $j = 0; $j < count($terms); $j++ ) {
if ( isset($terms[$j]['attr']) and $terms[$j]['attr'] == 'StorageId' and isset($terms[$j]['val']) ) {
$StorageArea = new Storage($terms[$j]['val']);
break;
}
} // end foreach remaining term
if ( ! $StorageArea ) $StorageArea = new Storage();
} // end no StorageArea found yet
$sqlValue = getDiskBlocks($StorageArea);
break;
default :
$sqlValue = $term['attr'];
break;
}
if ( $dtAttr ) {
$postfixExpr[] = array('type'=>'attr', 'value'=>$term['attr'], 'sqlValue'=>$sqlValue, 'dtAttr'=>true);
} else {
$postfixExpr[] = array('type'=>'attr', 'value'=>$term['attr'], 'sqlValue'=>$sqlValue);
}
} # end if attr
$sqlValue = '';
if ( isset($term['op']) ) {
if ( empty($term['op']) ) {
$term['op'] = '=';
}
switch ( $term['op']) {
case '=' :
case '!=' :
case '>=' :
case '>' :
case '<' :
case '<=' :
case 'LIKE' :
case 'NOT LIKE':
$sqlValue = $term['op'];
break;
case '=~' :
$sqlValue = 'regexp';
break;
case '!~' :
$sqlValue = 'not regexp';
break;
case '=[]' :
case 'IN' :
$sqlValue = 'in (';
break;
case '![]' :
$sqlValue = 'not in (';
break;
case 'IS' :
case 'IS NOT' :
if ( $term['val'] == 'Odd' ) {
$sqlValue = ' % 2 = 1';
} else if ( $term['val'] == 'Even' ) {
$sqlValue = ' % 2 = 0';
} else {
$sqlValue = ' '.$term['op'];
}
break;
default :
ZM\Error('Unknown operator in filter '.$term['op']);
}
while ( true ) {
if ( !count($postfixStack) ) {
$postfixStack[] = array('type'=>'op', 'value'=>$term['op'], 'sqlValue'=>$sqlValue);
break;
} else if ( $postfixStack[count($postfixStack)-1]['type'] == 'obr' ) {
$postfixStack[] = array('type'=>'op', 'value'=>$term['op'], 'sqlValue'=>$sqlValue);
break;
} else if ( $priorities[$term['op']] < $priorities[$postfixStack[count($postfixStack)-1]['value']] ) {
$postfixStack[] = array('type'=>'op', 'value'=>$term['op'], 'sqlValue'=>$sqlValue );
break;
} else {
$postfixExpr[] = array_pop($postfixStack);
}
} // end while
} // end if operator
if ( isset($term['val']) ) {
$valueList = array();
foreach ( preg_split('/["\'\s]*?,["\'\s]*?/', preg_replace('/^["\']+?(.+)["\']+?$/', '$1', $term['val'])) as $value ) {
$value_upper = strtoupper($value);
switch ( $term['attr'] ) {
case 'MonitorName':
case 'Name':
case 'Cause':
case 'Notes':
if ( $term['op'] == 'LIKE' || $term['op'] == 'NOT LIKE' ) {
$value = '%'.$value.'%';
}
$value = dbEscape($value);
break;
case 'MonitorServerId':
case 'FilterServerId':
case 'StorageServerId':
case 'ServerId':
if ( $value_upper == 'ZM_SERVER_ID' ) {
$value = ZM_SERVER_ID;
} else if ( $value_upper == 'NULL' ) {
} else {
$value = dbEscape($value);
}
break;
case 'StorageId':
$StorageArea = new Storage($value);
if ( $value != 'NULL' )
$value = dbEscape($value);
break;
case 'DateTime':
case 'EndDateTime':
case 'StartDateTime':
if ( $value_upper != 'NULL' )
$value = "'".date(STRF_FMT_DATETIME_DB, strtotime($value))."'";
break;
case 'Date':
case 'EndDate':
case 'StartDate':
$value = 'to_days(\''.date(STRF_FMT_DATETIME_DB, strtotime($value)).'\')';
break;
case 'Time':
case 'EndTime':
case 'StartTime':
$value = 'extract(hour_second from \''.date(STRF_FMT_DATETIME_DB, strtotime($value)).'\')';
break;
default :
if ( $value_upper != 'NULL' )
$value = dbEscape($value);
} // end switch attribute
$valueList[] = $value;
} // end foreach value
$postfixExpr[] = array('type'=>'val', 'value'=>$term['val'], 'sqlValue'=>join(',', $valueList));
} // end if has val
if ( !empty($term['cbr']) ) {
for ( $j = 0; $j < $term['cbr']; $j++ ) {
while ( count($postfixStack) ) {
$element = array_pop($postfixStack);
if ( $element['type'] == 'obr' ) {
$postfixExpr[count($postfixExpr)-1]['bracket'] = true;
break;
}
$postfixExpr[] = $element;
}
}
} #end if cbr
} # end foreach term
while ( count($postfixStack) ) {
$postfixExpr[] = array_pop($postfixStack);
}
$exprStack = array();
foreach ( $postfixExpr as $element ) {
if ( $element['type'] == 'attr' || $element['type'] == 'val' ) {
$node = array('data'=>$element, 'count'=>0);
$exprStack[] = $node;
} elseif ( $element['type'] == 'op' || $element['type'] == 'cnj' ) {
$right = array_pop($exprStack);
$left = array_pop($exprStack);
$node = array('data'=>$element, 'count'=>2+$left['count']+$right['count'], 'right'=>$right, 'left'=>$left);
$exprStack[] = $node;
} else {
ZM\Fatal('Unexpected element type \''.$element['type'].'\', value \''.$element['value'].'\'');
}
}
if ( count($exprStack) != 1 ) {
ZM\Fatal('Expression stack has '.count($exprStack).' elements');
}
return array_pop($exprStack);
} # end function tree
function addTerm($term=false, $position=null) {
if ( !FilterTerm::is_valid_attr($term['attr']) ) {
Error('Unsupported filter attribute ' . $term['attr']);
//return $this;
}
$terms = $this->terms();
if ( (!isset($position)) or ($position > count($terms)) )
$position = count($terms);
else if ( $position < 0 )
$position = 0;
if ( $term && ($position == 0) ) {
# if only 1 term, don't need AND or OR
unset($term['cnj']);
}
array_splice($terms, $position, 0, array($term ? $term : array()));
$this->terms($terms);
return $this;
} # end function addTerm
function addTerms($terms, $options=null) {
foreach ( $terms as $term ) {
$this->addTerm($term);
}
return $this;
}
function Events() {
$events = array();
if (!$this->test_pre_sql_conditions()) {
ZM\Debug('Pre conditions failed, not doing sql');
return $events;
}
$where = $this->sql() ? ' WHERE ('.$this->sql().')' : '';
$sort = $this->sort_field() ? $this->sort_field() .' '.($this->sort_asc() ? 'ASC' : 'DESC') : '';
$col_str = 'E.*, M.Name AS Monitor';
$sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'.$where.($sort?' ORDER BY '.$sort:'');
if ($this->limit() and !count($this->pre_sql_conditions()) and !count($this->post_sql_conditions())) {
$sql .= ' LIMIT '.$this->limit();
}
Debug('Calling the following sql query: ' .$sql);
$query = dbQuery($sql);
if (!$query) return $events;
while ($row = dbFetchNext($query)) {
$event = new Event($row);
$event->remove_from_cache();
if (!$this->test_post_sql_conditions($event)) {
continue;
}
$events[] = $event;
if ($this->limit() and (count($events) > $this->limit())) {
break;
}
} # end foreach row
return $events;
} # end Events()
} # end class Filter
?>