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='&amp;') {
58    if ( (! isset($this->_querystring)) or ( $separator != '&amp;' ) 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='&amp;') {
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