1<?php 2namespace ZM; 3require_once('Object.php'); 4require_once('FilterTerm.php'); 5 6class Filter extends ZM_Object { 7 protected static $table = 'Filters'; 8 9 protected $defaults = array( 10 'Id' => null, 11 'Name' => '', 12 'AutoExecute' => 0, 13 'AutoExecuteCmd' => '', 14 'AutoEmail' => 0, 15 'EmailTo' => '', 16 'EmailSubject' => '', 17 'EmailBody' => '', 18 'AutoDelete' => 0, 19 'AutoArchive' => 0, 20 'AutoUnarchive' => 0, 21 'AutoVideo' => 0, 22 'AutoUpload' => 0, 23 'AutoMessage' => 0, 24 'AutoMove' => 0, 25 'AutoMoveTo' => 0, 26 'AutoCopy' => 0, 27 'AutoCopyTo' => 0, 28 'UpdateDiskSpace' => 0, 29 'UserId' => 0, 30 'Background' => 0, 31 'Concurrent' => 0, 32 'Query_json' => '', 33 'LockRows' => 0, 34 ); 35 36 protected $_querystring; 37 protected $_sql; 38 protected $_hidden_fields; 39 public $_pre_sql_conditions; 40 public $_post_sql_conditions; 41 protected $_Terms; 42 43 public function sql() { 44 if ( ! isset($this->_sql) ) { 45 $this->_sql = ''; 46 foreach ( $this->FilterTerms() as $term ) { 47 #if ( ! ($term->is_pre_sql() or $term->is_post_sql()) ) { 48 $this->_sql .= $term->sql(); 49 #} else { 50 #$this->_sql .= '1'; 51 #} 52 } # end foreach term 53 } 54 return $this->_sql; 55 } 56 57 public function querystring($objectname='filter', $separator='&') { 58 if ( (! isset($this->_querystring)) or ( $separator != '&' ) or ($objectname != 'filter') ) { 59 $this->_querystring = ''; 60 foreach ( $this->FilterTerms() as $term ) { 61 $this->_querystring .= $term->querystring($objectname, $separator); 62 } # end foreach term 63 if ( $this->Id() ) { 64 $this->_querystring .= $separator.$objectname.urlencode('[Id]').'='.$this->Id(); 65 } 66 } 67 return $this->_querystring; 68 } 69 70 public function hidden_fields() { 71 if ( ! isset($this->_hidden_fields) ) { 72 $this->_hidden_fields = ''; 73 foreach ( $this->FilterTerms() as $term ) { 74 $this->_hidden_fields .= $term->hidden_fields(); 75 } # end foreach term 76 } 77 return $this->_hidden_fields; 78 } 79 80 public function pre_sql_conditions() { 81 if ( ! isset($this->_pre_sql_conditions) ) { 82 $this->_pre_sql_conditions = array(); 83 foreach ( $this->FilterTerms() as $term ) { 84 if ( $term->is_pre_sql() ) 85 $this->_pre_sql_conditions[] = $term; 86 } # end foreach term 87 } 88 return $this->_pre_sql_conditions; 89 } 90 91 public function post_sql_conditions() { 92 93 if ( ! isset($this->_post_sql_conditions) ) { 94 $this->_post_sql_conditions = array(); 95 foreach ( $this->FilterTerms() as $term ) { 96 if ( $term->is_post_sql() ) 97 $this->_post_sql_conditions[] = $term; 98 } # end foreach term 99 } 100 return $this->_post_sql_conditions; 101 } 102 103 public function FilterTerms() { 104 if ( ! isset($this->Terms) ) { 105 $this->Terms = array(); 106 $_terms = $this->terms(); 107 for ( $i = 0; $i < count($_terms); $i++ ) { 108 $term = new FilterTerm($this, $_terms[$i], $i); 109 $this->Terms[] = $term; 110 } # end foreach term 111 } 112 return $this->Terms; 113 } 114 115 public static function parse($new_filter, $querySep='&') { 116 $filter = new Filter(); 117 $filter->Query($new_filter['Query']); 118 return $filter; 119 } 120 121 # If no storage areas are specified in the terms, then return all 122 public function get_StorageAreas() { 123 $storage_ids = array(); 124 foreach ( $this->Terms as $term ) { 125 if ( $term->attr == 'StorageId' ) { 126 # TODO handle other operators like != 127 $storage_ids[] = $term->value; 128 } 129 } 130 if ( count($storage_ids) ) { 131 return Storage::find(array('Id'=>$storage_ids)); 132 } else { 133 return Storage::find(); 134 } 135 } # end function get_StorageAreas 136 137 public function Query_json() { 138 if ( func_num_args( ) ) { 139 $this->{'Query_json'} = func_get_arg(0); 140 $this->{'Query'} = jsonDecode($this->{'Query_json'}); 141 } 142 return $this->{'Query_json'}; 143 } 144 145 public function Query() { 146 if ( func_num_args( ) ) { 147 $this->{'Query'} = func_get_arg(0); 148 $this->{'Query_json'} = jsonEncode($this->{'Query'}); 149 # We have altered the query so need to reset all the calculated results. 150 unset($this->_querystring); 151 unset($this->_sql); 152 unset($this->_hidden_fields); 153 unset($this->_pre_sql_conditions); 154 unset($this->_post_sql_conditions); 155 unset($this->_Terms); 156 } 157 if ( !property_exists($this, 'Query') ) { 158 if ( property_exists($this, 'Query_json') and $this->{'Query_json'} ) { 159 $this->{'Query'} = jsonDecode($this->{'Query_json'}); 160 } else { 161 $this->{'Query'} = array(); 162 } 163 } else { 164 if ( !is_array($this->{'Query'}) ) { 165 # Handle existence of both Query_json and Query in the row 166 $this->{'Query'} = jsonDecode($this->{'Query_json'}); 167 } 168 } 169 return $this->{'Query'}; 170 } 171 172 public static function find( $parameters = array(), $options = array() ) { 173 return ZM_Object::_find(get_class(), $parameters, $options); 174 } 175 176 public static function find_one( $parameters = array(), $options = array() ) { 177 return ZM_Object::_find_one(get_class(), $parameters, $options); 178 } 179 180 public function terms( ) { 181 if ( func_num_args() ) { 182 $Query = $this->Query(); 183 $Query['terms'] = func_get_arg(0); 184 $this->Query($Query); 185 } 186 if ( isset( $this->Query()['terms'] ) ) { 187 return $this->Query()['terms']; 188 } 189 return array(); 190 } 191 192 // The following three fields are actually stored in the Query 193 public function sort_field( ) { 194 if ( func_num_args( ) ) { 195 $Query = $this->Query(); 196 $Query['sort_field'] = func_get_arg(0); 197 $this->Query($Query); 198 } 199 if ( isset( $this->Query()['sort_field'] ) ) { 200 return $this->{'Query'}['sort_field']; 201 } 202 return ZM_WEB_EVENT_SORT_FIELD; 203 #return $this->defaults{'sort_field'}; 204 } 205 206 public function sort_asc( ) { 207 if ( func_num_args( ) ) { 208 $Query = $this->Query(); 209 $Query['sort_asc'] = func_get_arg(0); 210 $this->Query($Query); 211 } 212 if ( isset( $this->Query()['sort_asc'] ) ) { 213 return $this->{'Query'}['sort_asc']; 214 } 215 return ZM_WEB_EVENT_SORT_ORDER == 'asc' ? 1 : 0; 216 #return $this->defaults{'sort_asc'}; 217 } 218 219 public function limit( ) { 220 if ( func_num_args( ) ) { 221 $Query = $this->Query(); 222 $Query['limit'] = func_get_arg(0); 223 $this->Query($Query); 224 } 225 if ( isset( $this->Query()['limit'] ) ) 226 return $this->{'Query'}['limit']; 227 return 100; 228 #return $this->defaults{'limit'}; 229 } 230 231 public function control($command, $server_id=null) { 232 $Servers = $server_id ? Server::find(array('Id'=>$server_id)) : Server::find(array('Status'=>'Running')); 233 if ( !count($Servers) ) { 234 if ( !$server_id ) { 235 # This will be the non-multi-server case 236 $Servers = array(new Server()); 237 } else { 238 Warning("Server not found for id $server_id"); 239 } 240 } 241 foreach ( $Servers as $Server ) { 242 243 if ( (!defined('ZM_SERVER_ID')) or (!$Server->Id()) or (ZM_SERVER_ID==$Server->Id()) ) { 244 # Local 245 Debug("Controlling filter locally $command for server ".$Server->Id()); 246 daemonControl($command, 'zmfilter.pl', '--filter_id='.$this->{'Id'}.' --daemon'); 247 } else { 248 # Remote case 249 250 $url = $Server->UrlToIndex(); 251 if ( ZM_OPT_USE_AUTH ) { 252 if ( ZM_AUTH_RELAY == 'hashed' ) { 253 $url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS); 254 } else if ( ZM_AUTH_RELAY == 'plain' ) { 255 $url = '?user='.$_SESSION['username']; 256 $url = '?pass='.$_SESSION['password']; 257 } else if ( ZM_AUTH_RELAY == 'none' ) { 258 $url = '?user='.$_SESSION['username']; 259 } 260 } 261 $url .= '&view=filter&object=filter&action=control&command='.$command.'&Id='.$this->Id().'&ServerId='.$Server->Id(); 262 Debug("sending command to $url"); 263 $data = array(); 264 if ( defined('ZM_ENABLE_CSRF_MAGIC') ) { 265 require_once( 'includes/csrf/csrf-magic.php' ); 266 $data['__csrf_magic'] = csrf_get_tokens(); 267 } 268 269 // use key 'http' even if you send the request to https://... 270 $options = array( 271 'http' => array( 272 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 273 'method' => 'POST', 274 'content' => http_build_query($data) 275 ) 276 ); 277 $context = stream_context_create($options); 278 try { 279 $result = file_get_contents($url, false, $context); 280 if ( $result === FALSE ) { /* Handle error */ 281 Error("Error restarting zmfilter.pl using $url"); 282 } 283 } catch ( Exception $e ) { 284 Error("Except $e thrown trying to restart zmfilter"); 285 } 286 } # end if local or remote 287 } # end foreach erver 288 } # end function control 289 290 public function execute() { 291 $command = ZM_PATH_BIN.'/zmfilter.pl --filter_id='.escapeshellarg($this->Id()); 292 $result = exec($command, $output, $status); 293 Debug("$command status:$status output:".implode("\n", $output)); 294 return $status; 295 } 296 297 public function test_pre_sql_conditions() { 298 if ( !count($this->pre_sql_conditions()) ) { 299 return true; 300 } # end if pre_sql_conditions 301 302 $failed = false; 303 foreach ( $this->pre_sql_conditions() as $term ) { 304 if ( !$term->test() ) { 305 $failed = true; 306 break; 307 } 308 } 309 return $failed; 310 } 311 312 public function test_post_sql_conditions($event) { 313 if ( !count($this->post_sql_conditions()) ) { 314 return true; 315 } # end if pre_sql_conditions 316 317 $failed = true; 318 foreach ( $this->post_sql_conditions() as $term ) { 319 if ( !$term->test($event) ) { 320 $failed = false; 321 break; 322 } 323 } 324 return $failed; 325 } 326 327 function tree() { 328 $terms = $this->terms(); 329 330 if ( count($terms) <= 0 ) { 331 return false; 332 } 333 334 $StorageArea = NULL; 335 336 $postfixExpr = array(); 337 $postfixStack = array(); 338 339 $priorities = array( 340 '<' => 1, 341 '<=' => 1, 342 '>' => 1, 343 '>=' => 1, 344 '=' => 2, 345 '!=' => 2, 346 '=~' => 2, 347 '!~' => 2, 348 '=[]' => 2, 349 '![]' => 2, 350 'and' => 3, 351 'or' => 4, 352 'IS' => 2, 353 'IS NOT' => 2, 354 ); 355 356 for ( $i = 0; $i < count($terms); $i++ ) { 357 $term = $terms[$i]; 358 if ( !empty($term['cnj']) ) { 359 while ( true ) { 360 if ( !count($postfixStack) ) { 361 $postfixStack[] = array('type'=>'cnj', 'value'=>$term['cnj'], 'sqlValue'=>$term['cnj']); 362 break; 363 } elseif ( $postfixStack[count($postfixStack)-1]['type'] == 'obr' ) { 364 $postfixStack[] = array('type'=>'cnj', 'value'=>$term['cnj'], 'sqlValue'=>$term['cnj']); 365 break; 366 } elseif ( $priorities[$term['cnj']] < $priorities[$postfixStack[count($postfixStack)-1]['value']] ) { 367 $postfixStack[] = array('type'=>'cnj', 'value'=>$term['cnj'], 'sqlValue'=>$term['cnj']); 368 break; 369 } else { 370 $postfixExpr[] = array_pop($postfixStack); 371 } 372 } 373 } # end if ! empty cnj 374 375 if ( !empty($term['obr']) ) { 376 for ( $j = 0; $j < $term['obr']; $j++ ) { 377 $postfixStack[] = array('type'=>'obr', 'value'=>$term['obr']); 378 } 379 } 380 if ( !empty($term['attr']) ) { 381 $dtAttr = false; 382 switch ( $term['attr']) { 383 case 'MonitorName': 384 $sqlValue = 'M.'.preg_replace( '/^Monitor/', '', $term['attr']); 385 break; 386 case 'ServerId': 387 $sqlValue .= 'M.ServerId'; 388 break; 389 case 'StorageServerId': 390 $sqlValue .= 'S.ServerId'; 391 break; 392 case 'FilterServerId': 393 $sqlValue .= ZM_SERVER_ID; 394 break; 395 case 'DateTime': 396 case 'StartDateTime': 397 $sqlValue = 'E.StartDateTime'; 398 $dtAttr = true; 399 break; 400 case 'Date': 401 case 'StartDate': 402 $sqlValue = 'to_days(E.StartDateTime)'; 403 $dtAttr = true; 404 break; 405 case 'Time': 406 case 'StartTime': 407 $sqlValue = 'extract(hour_second from E.StartDateTime)'; 408 break; 409 case 'Weekday': 410 case 'StartWeekday': 411 $sqlValue = 'weekday(E.StartDateTime)'; 412 break; 413 case 'EndDateTime': 414 $sqlValue = 'E.EndDateTime'; 415 $dtAttr = true; 416 break; 417 case 'EndDate': 418 $sqlValue = 'to_days(E.EndDateTime)'; 419 $dtAttr = true; 420 break; 421 case 'EndTime': 422 $sqlValue = 'extract(hour_second from E.EndDateTime)'; 423 break; 424 case 'EndWeekday': 425 $sqlValue = 'weekday(E.EndDateTime)'; 426 break; 427 case 'Id': 428 case 'Name': 429 case 'MonitorId': 430 case 'StorageId': 431 case 'SecondaryStorageId': 432 case 'Length': 433 case 'Frames': 434 case 'AlarmFrames': 435 case 'TotScore': 436 case 'AvgScore': 437 case 'MaxScore': 438 case 'Cause': 439 case 'Notes': 440 case 'StateId': 441 case 'Archived': 442 $sqlValue = 'E.'.$term['attr']; 443 break; 444 case 'DiskPercent': 445 // 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 446 if ( ! $StorageArea ) { 447 for ( $j = 0; $j < count($terms); $j++ ) { 448 if ( isset($terms[$j]['attr']) and $terms[$j]['attr'] == 'StorageId' and isset($terms[$j]['val']) ) { 449 $StorageArea = new Storage($terms[$j]['val']); 450 break; 451 } 452 } // end foreach remaining term 453 if ( ! $StorageArea ) $StorageArea = new Storage(); 454 } // end no StorageArea found yet 455 $sqlValue = getDiskPercent($StorageArea); 456 break; 457 case 'DiskBlocks': 458 // 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 459 if ( ! $StorageArea ) { 460 for ( $j = 0; $j < count($terms); $j++ ) { 461 if ( isset($terms[$j]['attr']) and $terms[$j]['attr'] == 'StorageId' and isset($terms[$j]['val']) ) { 462 $StorageArea = new Storage($terms[$j]['val']); 463 break; 464 } 465 } // end foreach remaining term 466 if ( ! $StorageArea ) $StorageArea = new Storage(); 467 } // end no StorageArea found yet 468 $sqlValue = getDiskBlocks($StorageArea); 469 break; 470 default : 471 $sqlValue = $term['attr']; 472 break; 473 } 474 if ( $dtAttr ) { 475 $postfixExpr[] = array('type'=>'attr', 'value'=>$term['attr'], 'sqlValue'=>$sqlValue, 'dtAttr'=>true); 476 } else { 477 $postfixExpr[] = array('type'=>'attr', 'value'=>$term['attr'], 'sqlValue'=>$sqlValue); 478 } 479 } # end if attr 480 481 $sqlValue = ''; 482 if ( isset($term['op']) ) { 483 if ( empty($term['op']) ) { 484 $term['op'] = '='; 485 } 486 switch ( $term['op']) { 487 case '=' : 488 case '!=' : 489 case '>=' : 490 case '>' : 491 case '<' : 492 case '<=' : 493 case 'LIKE' : 494 case 'NOT LIKE': 495 $sqlValue = $term['op']; 496 break; 497 case '=~' : 498 $sqlValue = 'regexp'; 499 break; 500 case '!~' : 501 $sqlValue = 'not regexp'; 502 break; 503 case '=[]' : 504 case 'IN' : 505 $sqlValue = 'in ('; 506 break; 507 case '![]' : 508 $sqlValue = 'not in ('; 509 break; 510 case 'IS' : 511 case 'IS NOT' : 512 if ( $term['val'] == 'Odd' ) { 513 $sqlValue = ' % 2 = 1'; 514 } else if ( $term['val'] == 'Even' ) { 515 $sqlValue = ' % 2 = 0'; 516 } else { 517 $sqlValue = ' '.$term['op']; 518 } 519 break; 520 default : 521 ZM\Error('Unknown operator in filter '.$term['op']); 522 } 523 524 while ( true ) { 525 if ( !count($postfixStack) ) { 526 $postfixStack[] = array('type'=>'op', 'value'=>$term['op'], 'sqlValue'=>$sqlValue); 527 break; 528 } else if ( $postfixStack[count($postfixStack)-1]['type'] == 'obr' ) { 529 $postfixStack[] = array('type'=>'op', 'value'=>$term['op'], 'sqlValue'=>$sqlValue); 530 break; 531 } else if ( $priorities[$term['op']] < $priorities[$postfixStack[count($postfixStack)-1]['value']] ) { 532 $postfixStack[] = array('type'=>'op', 'value'=>$term['op'], 'sqlValue'=>$sqlValue ); 533 break; 534 } else { 535 $postfixExpr[] = array_pop($postfixStack); 536 } 537 } // end while 538 } // end if operator 539 540 if ( isset($term['val']) ) { 541 $valueList = array(); 542 foreach ( preg_split('/["\'\s]*?,["\'\s]*?/', preg_replace('/^["\']+?(.+)["\']+?$/', '$1', $term['val'])) as $value ) { 543 $value_upper = strtoupper($value); 544 switch ( $term['attr'] ) { 545 case 'MonitorName': 546 case 'Name': 547 case 'Cause': 548 case 'Notes': 549 if ( $term['op'] == 'LIKE' || $term['op'] == 'NOT LIKE' ) { 550 $value = '%'.$value.'%'; 551 } 552 $value = dbEscape($value); 553 break; 554 case 'MonitorServerId': 555 case 'FilterServerId': 556 case 'StorageServerId': 557 case 'ServerId': 558 if ( $value_upper == 'ZM_SERVER_ID' ) { 559 $value = ZM_SERVER_ID; 560 } else if ( $value_upper == 'NULL' ) { 561 562 } else { 563 $value = dbEscape($value); 564 } 565 break; 566 case 'StorageId': 567 $StorageArea = new Storage($value); 568 if ( $value != 'NULL' ) 569 $value = dbEscape($value); 570 break; 571 case 'DateTime': 572 case 'EndDateTime': 573 case 'StartDateTime': 574 if ( $value_upper != 'NULL' ) 575 $value = "'".strftime(STRF_FMT_DATETIME_DB, strtotime($value))."'"; 576 break; 577 case 'Date': 578 case 'EndDate': 579 case 'StartDate': 580 $value = 'to_days(\''.strftime(STRF_FMT_DATETIME_DB, strtotime($value)).'\')'; 581 break; 582 case 'Time': 583 case 'EndTime': 584 case 'StartTime': 585 $value = 'extract(hour_second from \''.strftime(STRF_FMT_DATETIME_DB, strtotime($value)).'\')'; 586 break; 587 default : 588 if ( $value_upper != 'NULL' ) 589 $value = dbEscape($value); 590 } // end switch attribute 591 $valueList[] = $value; 592 } // end foreach value 593 $postfixExpr[] = array('type'=>'val', 'value'=>$term['val'], 'sqlValue'=>join(',', $valueList)); 594 } // end if has val 595 596 if ( !empty($term['cbr']) ) { 597 for ( $j = 0; $j < $term['cbr']; $j++ ) { 598 while ( count($postfixStack) ) { 599 $element = array_pop($postfixStack); 600 if ( $element['type'] == 'obr' ) { 601 $postfixExpr[count($postfixExpr)-1]['bracket'] = true; 602 break; 603 } 604 $postfixExpr[] = $element; 605 } 606 } 607 } #end if cbr 608 } # end foreach term 609 610 while ( count($postfixStack) ) { 611 $postfixExpr[] = array_pop($postfixStack); 612 } 613 614 $exprStack = array(); 615 foreach ( $postfixExpr as $element ) { 616 if ( $element['type'] == 'attr' || $element['type'] == 'val' ) { 617 $node = array('data'=>$element, 'count'=>0); 618 $exprStack[] = $node; 619 } elseif ( $element['type'] == 'op' || $element['type'] == 'cnj' ) { 620 $right = array_pop($exprStack); 621 $left = array_pop($exprStack); 622 $node = array('data'=>$element, 'count'=>2+$left['count']+$right['count'], 'right'=>$right, 'left'=>$left); 623 $exprStack[] = $node; 624 } else { 625 ZM\Fatal('Unexpected element type \''.$element['type'].'\', value \''.$element['value'].'\''); 626 } 627 } 628 if ( count($exprStack) != 1 ) { 629 ZM\Fatal('Expression stack has '.count($exprStack).' elements'); 630 } 631 return array_pop($exprStack); 632 } # end function tree 633 634 function addTerm($term=false, $position=null) { 635 636 if ( !FilterTerm::is_valid_attr($term['attr']) ) { 637 Error('Unsupported filter attribute ' . $term['attr']); 638 //return $this; 639 } 640 641 $terms = $this->terms(); 642 643 if ( (!isset($position)) or ($position > count($terms)) ) 644 $position = count($terms); 645 else if ( $position < 0 ) 646 $position = 0; 647 648 if ( $term && ($position == 0) ) { 649 # if only 1 term, don't need AND or OR 650 unset($term['cnj']); 651 } 652 653 array_splice($terms, $position, 0, array($term ? $term : array())); 654 $this->terms($terms); 655 656 return $this; 657 } # end function addTerm 658 659 function addTerms($terms, $options=null) { 660 foreach ( $terms as $term ) { 661 $this->addTerm($term); 662 } 663 return $this; 664 } 665 666} # end class Filter 667?> 668