1<?php
2//
3// ZoneMinder web timeline view file, $Date$, $Revision$
4//
5// This program is free software; you can redistribute it and/or
6// modify it under the terms of the GNU General Public License
7// as published by the Free Software Foundation; either version 2
8// of the License, or (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program; if not, write to the Free Software
17// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18//
19
20if ( !canView('Events') ) {
21  $view = 'error';
22  return;
23}
24
25foreach ( getSkinIncludes('includes/timeline_functions.php') as $includeFile )
26  require_once $includeFile;
27
28//
29// Date/time formats used in charts
30//
31// These are the time axis range text. The first of each pair is the start date/time
32// and the second is the last so often contains additional information
33//
34
35// When the chart range is years
36define( 'STRF_TL_AXIS_RANGE_YEAR1', '%b %Y' );
37define( 'STRF_TL_AXIS_RANGE_YEAR2', STRF_TL_AXIS_RANGE_YEAR1 );
38
39// When the chart range is months
40define( 'STRF_TL_AXIS_RANGE_MONTH1', '%b' );
41define( 'STRF_TL_AXIS_RANGE_MONTH2', STRF_TL_AXIS_RANGE_MONTH1.' %Y' );
42
43// When the chart range is days
44define( 'STRF_TL_AXIS_RANGE_DAY1', '%d' );
45define( 'STRF_TL_AXIS_RANGE_DAY2', STRF_TL_AXIS_RANGE_DAY1.' %b %Y' );
46
47// When the chart range is less than a day
48define( 'STRF_TL_AXIS_RANGE_TIME1', '%H:%M' );
49define( 'STRF_TL_AXIS_RANGE_TIME2', STRF_TL_AXIS_RANGE_TIME1.', %d %b %Y' );
50
51//
52// These are the time axis tick labels
53//
54define( 'STRF_TL_AXIS_LABEL_YEAR', '%Y' );
55define( 'STRF_TL_AXIS_LABEL_MONTH', '%M' );
56define( 'STRF_TL_AXIS_LABEL_WEEK', '%d/%m' );
57define( 'STRF_TL_AXIS_LABEL_DAY', '%d' );
58define( 'STRF_TL_AXIS_LABEL_4HOUR', '%H:00' );
59define( 'STRF_TL_AXIS_LABEL_HOUR', '%H:00' );
60define( 'STRF_TL_AXIS_LABEL_10MINUTE', '%H:%M' );
61define( 'STRF_TL_AXIS_LABEL_MINUTE', '%H:%M' );
62define( 'STRF_TL_AXIS_LABEL_10SECOND', '%S' );
63define( 'STRF_TL_AXIS_LABEL_SECOND', '%S' );
64
65$mouseover = isset($_REQUEST['mouseover']) ? $_REQUEST['mouseover'] : true;
66
67$mode = isset($_REQUEST['mode']) ? $_REQUEST['mode'] : 'overlay';
68
69$minEventWidth = 3;
70$maxEventWidth = 6;
71
72$chart = array(
73    'width'=>700,
74    'height'=>460,
75    'image' => array(
76      'width'=>264,
77      'height'=>220,
78      'topOffset'=>20,
79      ),
80    'imageText' => array(
81      'width'=>400,
82      'height'=>30,
83      'topOffset'=>20,
84      ),
85    'graph' => array(
86      'width'=>600,
87      'height'=>160,
88      'topOffset'=>30,
89      ),
90    'title' => array(
91      'topOffset'=>50
92      ),
93    'key' => array(
94        'topOffset'=>50
95        ),
96    'axes' => array(
97        'x' => array(
98          'height' => 20,
99          ),
100        'y' => array(
101          'width' => 30,
102          ),
103        ),
104    'grid' => array(
105        'x' => array(
106          'major' => array(
107            'max' => 12,
108            'min' => 4,
109            ),
110          'minor' => array(
111            'max' => 48,
112            'min' => 12,
113            ),
114          ),
115        'y' => array(
116          'major' => array(
117            'max' => 8,
118            'min' => 1,
119            ),
120          'minor' => array(
121            'max' => 0,
122            'min' => 0,
123            ),
124          ),
125        ),
126);
127
128$monitors = array();
129
130# The as E, and joining with Monitors is required for the filterSQL filters.
131$rangeSql = 'SELECT min(E.StartDateTime) AS MinTime, max(E.EndDateTime) AS MaxTime FROM Events AS E INNER JOIN Monitors AS M ON (E.MonitorId = M.Id) WHERE NOT isnull(E.StartDateTime) AND NOT isnull(E.EndDateTime)';
132$eventsSql = 'SELECT E.* FROM Events AS E INNER JOIN Monitors AS M ON (E.MonitorId = M.Id) WHERE NOT isnull(StartDateTime)';
133$eventIdsSql = 'SELECT E.Id FROM Events AS E INNER JOIN Monitors AS M ON (E.MonitorId = M.Id) WHERE NOT isnull(StartDateTime)';
134$eventsValues = array();
135
136if ( !empty($user['MonitorIds']) ) {
137  $monFilterSql = ' AND MonitorId IN ('.$user['MonitorIds'].')';
138
139  $rangeSql .= $monFilterSql;
140  $eventsSql .= $monFilterSql;
141  $eventIdsSql .= $monFilterSql;
142}
143
144$tree = false;
145if ( isset($_REQUEST['filter']) ) {
146  $filter =  ZM\Filter::parse($_REQUEST['filter']);
147  $tree = $filter->tree();
148}
149
150if ( isset($_REQUEST['range']) )
151  $range = validHtmlStr($_REQUEST['range']);
152if ( isset($_REQUEST['minTime']) )
153  $minTime = validHtmlStr($_REQUEST['minTime']);
154if ( isset($_REQUEST['midTime']) )
155  $midTime = validHtmlStr($_REQUEST['midTime']);
156if ( isset($_REQUEST['maxTime']) )
157  $maxTime = validHtmlStr($_REQUEST['maxTime']);
158
159if ( isset($range) and validInt($range) ) {
160  $halfRange = (int)($range/2);
161  if ( isset($midTime) ) {
162    $midTimeT = strtotime($midTime);
163    $minTimeT = $midTimeT-$halfRange;
164    $maxTimeT = $midTimeT+$halfRange;
165    if ( !($range%1) ) {
166      $maxTimeT--;
167    }
168    $minTime = strftime(STRF_FMT_DATETIME_DB, $minTimeT);
169    $maxTime = strftime(STRF_FMT_DATETIME_DB, $maxTimeT);
170  } elseif ( isset($minTime) ) {
171    $minTimeT = strtotime($minTime);
172    $maxTimeT = $minTimeT + $range;
173    $midTimeT = $minTimeT + $halfRange;
174    $midTime = strftime(STRF_FMT_DATETIME_DB, $midTimeT);
175    $maxTime = strftime(STRF_FMT_DATETIME_DB, $maxTimeT);
176  } elseif ( isset($maxTime) ) {
177    $maxTimeT = strtotime($maxTime);
178    $minTimeT = $maxTimeT - $range;
179    $midTimeT = $minTimeT + $halfRange;
180    $minTime = strftime(STRF_FMT_DATETIME_DB, $minTimeT);
181    $midTime = strftime(STRF_FMT_DATETIME_DB, $midTimeT);
182  }
183} elseif ( isset($minTime) && isset($maxTime) ) {
184  $minTimeT = strtotime($minTime);
185  $maxTimeT = strtotime($maxTime);
186  $range = ($maxTimeT - $minTimeT) + 1;
187  $halfRange = (int)($range/2);
188  $midTimeT = $minTimeT + $halfRange;
189  $midTime = strftime(STRF_FMT_DATETIME_DB, $midTimeT);
190}
191
192if ( isset($minTime) && isset($maxTime) ) {
193  $tempMinTime = $tempMaxTime = $tempExpandable = false;
194  extractDatetimeRange($tree, $tempMinTime, $tempMaxTime, $tempExpandable);
195  $filterSql = parseTreeToSQL($tree);
196
197  if ( $filterSql ) {
198    $eventsSql .= ' AND '.$filterSql;
199    $eventIdsSql .= ' AND '.$filterSql;
200  }
201} else {
202  $filterSql = parseTreeToSQL($tree);
203  $tempMinTime = $tempMaxTime = $tempExpandable = false;
204  extractDatetimeRange($tree, $tempMinTime, $tempMaxTime, $tempExpandable);
205
206  if ( $filterSql ) {
207    $rangeSql .= ' AND '.$filterSql;
208    $eventsSql .= ' AND '.$filterSql;
209    $eventIdsSql .= ' AND '.$filterSql;
210  }
211
212  if ( !isset($minTime) || !isset($maxTime) ) {
213    // Dynamically determine range
214    $row = dbFetchOne($rangeSql);
215    if ( $row ) {
216      if ( !isset($minTime) )
217        $minTime = $row['MinTime'];
218      if ( !isset($maxTime) )
219        $maxTime = $row['MaxTime'];
220    }
221  }
222
223  if ( empty($minTime) )
224    $minTime = $tempMinTime;
225  if ( empty($maxTime) )
226    $maxTime = $tempMaxTime;
227  if ( empty($maxTime) )
228    $maxTime = 'now';
229
230  $minTimeT = strtotime($minTime);
231  $maxTimeT = strtotime($maxTime);
232  $range = ($maxTimeT - $minTimeT) + 1;
233  $halfRange = (int)($range/2);
234  $midTimeT = $minTimeT + $halfRange;
235  $midTime = strftime(STRF_FMT_DATETIME_DB, $midTimeT);
236}
237
238if ( $tree ) {
239  appendDatetimeRange($tree, $minTime, $maxTime);
240
241  $filterQuery = parseTreeToQuery($tree);
242} else {
243  $filterQuery = false;
244}
245
246$scales = array(
247  array( 'name'=>'year',     'factor'=>60*60*24*365, 'align'=>1,  'zoomout'=>2,    'label'=>STRF_TL_AXIS_LABEL_YEAR ),
248  array( 'name'=>'month',    'factor'=>60*60*24*30,  'align'=>1,  'zoomout'=>12,   'label'=>STRF_TL_AXIS_LABEL_MONTH ),
249  array( 'name'=>'week',     'factor'=>60*60*24*7,   'align'=>1,  'zoomout'=>4.25, 'label'=>STRF_TL_AXIS_LABEL_WEEK,     'labelCheck'=>'%W' ),
250  array( 'name'=>'day',      'factor'=>60*60*24,     'align'=>1,  'zoomout'=>7,    'label'=>STRF_TL_AXIS_LABEL_DAY ),
251  array( 'name'=>'hour4',    'factor'=>60*60,        'align'=>4,  'zoomout'=>6,    'label'=>STRF_TL_AXIS_LABEL_4HOUR,    'labelCheck'=>'%H' ),
252  array( 'name'=>'hour',     'factor'=>60*60,        'align'=>1,  'zoomout'=>4,    'label'=>STRF_TL_AXIS_LABEL_HOUR,     'labelCheck'=>'%H' ),
253  array( 'name'=>'minute10', 'factor'=>60,           'align'=>10, 'zoomout'=>6,    'label'=>STRF_TL_AXIS_LABEL_10MINUTE, 'labelCheck'=>'%M' ),
254  array( 'name'=>'minute',   'factor'=>60,           'align'=>1,  'zoomout'=>10,   'label'=>STRF_TL_AXIS_LABEL_MINUTE,   'labelCheck'=>'%M' ),
255  array( 'name'=>'second10', 'factor'=>1,            'align'=>10, 'zoomout'=>6,    'label'=>STRF_TL_AXIS_LABEL_10SECOND ),
256  array( 'name'=>'second',   'factor'=>1,            'align'=>1,  'zoomout'=>10,   'label'=>STRF_TL_AXIS_LABEL_SECOND ),
257);
258
259$majXScale = getDateScale($scales, $range, $chart['grid']['x']['major']['min'], $chart['grid']['x']['major']['max']);
260
261// Adjust the range etc for scale
262$minTimeT -= $minTimeT%($majXScale['factor']*$majXScale['align']);
263$minTime = strftime(STRF_FMT_DATETIME_DB, $minTimeT);
264$maxTimeT += (($majXScale['factor']*$majXScale['align'])-$maxTimeT%($majXScale['factor']*$majXScale['align']))-1;
265if ( $maxTimeT > time() )
266  $maxTimeT = time();
267$maxTime = strftime(STRF_FMT_DATETIME_DB, $maxTimeT);
268$range = ($maxTimeT - $minTimeT) + 1;
269$halfRange = (int)($range/2);
270$midTimeT = $minTimeT + $halfRange;
271$midTime = strftime(STRF_FMT_DATETIME_DB, $midTimeT);
272
273if ( isset($minTime) && isset($maxTime) ) {
274  $eventsSql .= " AND EndDateTime >= '$minTime' AND StartDateTime <= '$maxTime'";
275  $eventIdsSql .= " AND EndDateTime >= '$minTime' AND StartDateTime <= '$maxTime'";
276}
277
278if ( 0 ) {
279$framesByEventId = array();
280$eventsSql .= ' ORDER BY E.Id ASC';
281$framesSql = "SELECT EventId,FrameId,Delta,Score FROM Frames WHERE EventId IN($eventIdsSql) AND Score > 0 ORDER BY Score DESC";
282$frames_result = dbQuery($framesSql);
283while ( $row = $frames_result->fetch(PDO::FETCH_ASSOC) ) {
284  if ( !isset($framesByEventId[$row['EventId']]) ) {
285    $framesByEventId[$row['EventId']] = array();
286  }
287  $framesByEventId[$row['EventId']][] = $row;
288}
289}
290
291
292$chart['data'] = array(
293  'x' => array(
294    'lo' => strtotime($minTime),
295    'hi' => strtotime($maxTime),
296  ),
297  'y' => array(
298    'lo' => 0,
299    'hi' => 0,
300  )
301);
302
303$chart['data']['x']['range'] = ($chart['data']['x']['hi'] - $chart['data']['x']['lo']) + 1;
304$chart['data']['x']['density'] = $chart['data']['x']['range']/$chart['graph']['width'];
305
306$monEventSlots = array();
307$monFrameSlots = array();
308$events_result = dbQuery($eventsSql);
309if ( !$events_result ) {
310  ZM\Fatal('SQL-ERR');
311  return;
312}
313
314$max_aspect_ratio = 0;
315
316while( $event = $events_result->fetch(PDO::FETCH_ASSOC) ) {
317  if ( !isset($monitors[$event['MonitorId']]) ) {
318    $monitor = $monitors[$event['MonitorId']] = ZM\Monitor::find_one(array('Id'=>$event['MonitorId']));
319    $monEventSlots[$event['MonitorId']] = array();
320    $monFrameSlots[$event['MonitorId']] = array();
321    $aspect_ratio = round($monitor->Width() / $monitor->Height(), 2);
322    if ( $aspect_ratio > $max_aspect_ratio )
323      $max_aspect_ratio = $aspect_ratio;
324  }
325
326  $currEventSlots = &$monEventSlots[$event['MonitorId']];
327  $currFrameSlots = &$monFrameSlots[$event['MonitorId']];
328
329  $startTimeT = strtotime($event['StartDateTime']);
330  $startIndex = $rawStartIndex = (int)(($startTimeT - $chart['data']['x']['lo']) / $chart['data']['x']['density']);
331  if ( $startIndex < 0 )
332    $startIndex = 0;
333
334  if ( isset($event['EndDateTime']) )
335    $endTimeT = strtotime($event['EndDateTime']);
336  else
337    $endTimeT = time();
338  $endIndex = $rawEndIndex = (int)(($endTimeT - $chart['data']['x']['lo']) / $chart['data']['x']['density']);
339
340  if ( $endIndex >= $chart['graph']['width'] )
341    $endIndex = $chart['graph']['width'] - 1;
342
343  for ( $i = $startIndex; $i <= $endIndex; $i++ ) {
344    if ( !isset($currEventSlots[$i]) ) {
345      if ( $rawStartIndex == $rawEndIndex ) {
346        $offset = 1;
347      } else {
348        $offset = 1 + ($event['Frames']?((int)(($event['Frames']-1)*(($i-$rawStartIndex)/($rawEndIndex-$rawStartIndex)))):0);
349      }
350      $currEventSlots[$i] = array( 'count'=>0, 'width'=>1, 'offset'=>$offset, 'event'=>$event );
351    } else {
352      $currEventSlots[$i]['count']++;
353    }
354  }
355
356  if ( $event['MaxScore'] > 0 ) {
357    if ( $startIndex == $endIndex ) {
358      # Only fills 1 slot, so just get the max Score
359      $framesSql = 'SELECT FrameId, Score FROM Frames WHERE EventId = ? AND Score > 0 ORDER BY Score DESC LIMIT 1';
360      $frame = dbFetchOne($framesSql, NULL, array($event['Id']));
361
362      $i = $startIndex;
363      if ( !isset($currFrameSlots[$i]) ) {
364        $currFrameSlots[$i] = array('count'=>1, 'value'=>$event['MaxScore'], 'event'=>$event, 'frame'=>$frame);
365      } else {
366        $currFrameSlots[$i]['count']++;
367        if ( $event['MaxScore'] > $currFrameSlots[$i]['value'] ) {
368          $currFrameSlots[$i]['value'] = $event['MaxScore'];
369          $currFrameSlots[$i]['event'] = $event;
370          $currFrameSlots[$i]['frame'] = $frame;
371        }
372      }
373      if ( $event['MaxScore'] > $chart['data']['y']['hi'] ) {
374        $chart['data']['y']['hi'] = $event['MaxScore'];
375      }
376    } else {
377      # Fills multiple Slots, so need multiple scores to generate the graph over multiple slots.
378      $framesSql = 'SELECT FrameId,Delta,Score FROM Frames WHERE EventId = ? AND Score > 0';
379      $result = dbQuery($framesSql, array($event['Id']));
380      while ( $frame = dbFetchNext($result) ) {
381      #foreach ( $framesByEventId[$event['Id']] as $frame ) {
382        $frameTimeT = $startTimeT + $frame['Delta'];
383        $frameIndex = (int)(($frameTimeT - $chart['data']['x']['lo']) / $chart['data']['x']['density']);
384        if ( $frameIndex < 0 )
385          continue;
386        if ( $frameIndex >= $chart['graph']['width'] )
387          continue;
388
389        if ( !isset($currFrameSlots[$frameIndex]) ) {
390          $currFrameSlots[$frameIndex] = array('count'=>1, 'value'=>$frame['Score'], 'event'=>$event, 'frame'=>$frame);
391        } else {
392          $currFrameSlots[$frameIndex]['count']++;
393          if ( $frame['Score'] > $currFrameSlots[$frameIndex]['value'] ) {
394            $currFrameSlots[$frameIndex]['value'] = $frame['Score'];
395            $currFrameSlots[$frameIndex]['event'] = $event;
396            $currFrameSlots[$frameIndex]['frame'] = $frame;
397          }
398        }
399        if ( $frame['Score'] > $chart['data']['y']['hi'] ) {
400          $chart['data']['y']['hi'] = $frame['Score'];
401        }
402      } // end foreach frame
403    }
404  } // end if MaxScore > 0
405} // end foreach event
406
407//ksort( $monitorIds, SORT_NUMERIC );
408ksort( $monEventSlots, SORT_NUMERIC );
409ksort( $monFrameSlots, SORT_NUMERIC );
410
411// No longer needed?
412if ( false ) {
413  // Add on missing frames
414  foreach( array_keys($monFrameSlots) as $monitorId ) {
415    unset( $currFrameSlots );
416    $currFrameSlots = &$monFrameSlots[$monitorId];
417    for ( $i = 0; $i < $chart['graph']['width']; $i++ ) {
418      if ( isset($currFrameSlots[$i]) ) {
419        if ( !isset($currFrameSlots[$i]['frame']) ) {
420          $framesSql = 'SELECT FrameId, Score FROM Frames WHERE EventId = ? AND Score > 0 ORDER BY FrameId LIMIT 1';
421          $currFrameSlots[$i]['frame'] = dbFetchOne( $framesSql, NULL, array( $currFrameSlots[$i]['event']['Id'] ) );
422        }
423      }
424    }
425  }
426}
427
428$chart['data']['y']['range'] = ($chart['data']['y']['hi'] - $chart['data']['y']['lo']) + 1;
429$chart['data']['y']['density'] = $chart['data']['y']['range']/$chart['graph']['height'];
430
431$majYScale = getYScale(
432  $chart['data']['y']['range'],
433  $chart['grid']['y']['major']['min'],
434  $chart['grid']['y']['major']['max']);
435
436// Optimise boxes
437foreach( array_keys($monEventSlots) as $monitorId ) {
438  unset( $currEventSlots );
439  $currEventSlots = &$monEventSlots[$monitorId];
440  for ( $i = 0; $i < $chart['graph']['width']; $i++ ) {
441    if ( isset($currEventSlots[$i]) ) {
442      if ( isset($currSlot) ) {
443        if ( $currSlot['event']['Id'] == $currEventSlots[$i]['event']['Id'] ) {
444          if ( $currSlot['width'] < $maxEventWidth ) {
445            // Merge slots for the same long event
446            $currSlot['width']++;
447            unset( $currEventSlots[$i] );
448            continue;
449          } else if ( $currSlot['offset'] < $currEventSlots[$i]['offset'] ) {
450            // Split very long events
451            $currEventSlots[$i]['frame'] = array( 'FrameId'=>$currEventSlots[$i]['offset'] );
452          }
453        } else if ( $currSlot['width'] < $minEventWidth ) {
454          // Merge multiple small events
455          $currSlot['width']++;
456          unset( $currEventSlots[$i] );
457          continue;
458        }
459      }
460      $currSlot = &$currEventSlots[$i];
461    } else {
462      unset($currSlot);
463    }
464  }  # end foreach x
465  unset($currSlot);
466} // end foreach Event Monitors
467//print_r( $monEventSlots );
468
469// Stack events
470$frameSlots = array();
471$frameMonitorIds = array_keys($monFrameSlots);
472for ( $i = 0; $i < $chart['graph']['width']; $i++ ) {
473  foreach ( $frameMonitorIds as $frameMonitorId ) {
474    $currFrameSlots = &$monFrameSlots[$frameMonitorId];
475    if ( isset($currFrameSlots[$i]) ) {
476      if ( !isset($frameSlots[$i]) ) {
477        $frameSlots[$i] = array();
478        $frameSlots[$i][] = &$currFrameSlots[$i];
479      } else {
480        $slotCount = count($frameSlots[$i]);
481        for ( $j = 0; $j < $slotCount; $j++ ) {
482          if ( $currFrameSlots[$i]['value'] > $frameSlots[$i][$j]['value'] ) {
483            for ( $k = $slotCount; $k > $j; $k-- ) {
484              $frameSlots[$i][$k] = $frameSlots[$i][$k-1];
485            }
486            $frameSlots[$i][$j] = &$currFrameSlots[$i];
487            break 2;
488          }
489        }
490        $frameSlots[$i][] = &$currFrameSlots[$i];
491      }
492    }
493    unset($currFrameSlots);
494  } # end foreach MonitorId
495}  # end foreach x
496
497//ZM\Debug(print_r( $monEventSlots,true ));
498//print_r( $monFrameSlots );
499//print_r( $chart );
500
501$graphHeight = $chart['graph']['height'];
502
503if ( $mode == 'overlay' ) {
504  $minEventBarHeight = 10;
505  $maxEventBarHeight = 40;
506
507  if ( count($monitors) ) {
508    $chart['graph']['eventBarHeight'] = $minEventBarHeight;
509    while ( ($chart['graph']['eventsHeight'] = (($chart['graph']['eventBarHeight'] * count($monitors)) + (count($monitors)-1))) < $maxEventBarHeight ) {
510      $chart['graph']['eventBarHeight']++;
511    }
512  } else {
513    $chart['graph']['eventBarHeight'] = $maxEventBarHeight;
514    $chart['graph']['eventsHeight'] = $maxEventBarHeight;
515  }
516  $chart['graph']['activityHeight'] = ($graphHeight - $chart['graph']['eventsHeight']);
517  $chart['data']['y']['density'] = $chart['data']['y']['range']/$chart['graph']['activityHeight'];
518
519  $chart['eventBars'] = array();
520  $top = $chart['graph']['activityHeight'];
521  foreach ( array_keys($monitors) as $monitorId ) {
522    $chart['eventBars'][$monitorId] = array( 'top' => $top );
523    $top += $chart['graph']['eventBarHeight']+1;
524  }
525} else if ( $mode == 'split' ) {
526  $minActivityBarHeight = 30;
527  $minEventBarHeight = 10;
528  $maxEventBarHeight = 40;
529
530  if ( count($monitors) ) {
531    $chart['graph']['eventBarHeight'] = $minEventBarHeight;
532    $chart['graph']['activityBarHeight'] = $minActivityBarHeight;
533    while ( ((($chart['graph']['eventBarHeight']+$chart['graph']['activityBarHeight']) * count($monitors)) + ((2*count($monitors))-1)) < $graphHeight ) {
534      $chart['graph']['activityBarHeight']++;
535      if ( $chart['graph']['eventBarHeight'] < $maxEventBarHeight ) {
536        $chart['graph']['eventBarHeight']++;
537      }
538    }
539  } else {
540    $chart['graph']['eventBarHeight'] = $maxEventBarHeight;
541    $chart['graph']['activityBarHeight'] = $graphHeight - $chart['graph']['eventBarHeight'];
542  }
543  $chart['data']['y']['density'] = $chart['data']['y']['range']/$chart['graph']['activityBarHeight'];
544
545  $chart['activityBars'] = array();
546  $chart['eventBars'] = array();
547  $top = 0;
548  $barCount = 1;
549  foreach ( array_keys($monitors) as $monitorId ) {
550    $chart['eventBars'][$monitorId] = array( 'top' => $top );
551    $chart['eventBars'][$monitorId] = array( 'top' => $top+$chart['graph']['activityBarHeight']+1 );
552    $top +=  $chart['graph']['activityBarHeight']+1+$chart['graph']['eventBarHeight']+1;
553  }
554} else {
555  ZM\Warning("No mode $mode");
556}
557
558preg_match('/^(\d+)-(\d+)-(\d+) (\d+):(\d+)/', $minTime, $startMatches);
559preg_match('/^(\d+)-(\d+)-(\d+) (\d+):(\d+)/', $maxTime, $endMatches);
560
561if ( $startMatches[1] != $endMatches[1] ) {
562  // Different years
563  $title = strftime( STRF_TL_AXIS_RANGE_YEAR1, $chart['data']['x']['lo'] ).' - '.strftime( STRF_TL_AXIS_RANGE_YEAR2, $chart['data']['x']['hi'] );
564} else if ( $startMatches[2] != $endMatches[2] ) {
565  // Different months
566  $title = strftime( STRF_TL_AXIS_RANGE_MONTH1, $chart['data']['x']['lo'] ).' - '.strftime( STRF_TL_AXIS_RANGE_MONTH2, $chart['data']['x']['hi'] );
567} else if ( $startMatches[3] != $endMatches[3] ) {
568  // Different dates
569  $title = strftime( STRF_TL_AXIS_RANGE_DAY1, $chart['data']['x']['lo'] ).' - '.strftime( STRF_TL_AXIS_RANGE_DAY2, $chart['data']['x']['hi'] );
570} else {
571  // Different times
572  $title = strftime( STRF_TL_AXIS_RANGE_TIME1, $chart['data']['x']['lo'] ).' - '.strftime( STRF_TL_AXIS_RANGE_TIME2, $chart['data']['x']['hi'] );
573}
574
575function drawXGrid( $chart, $scale, $labelClass, $tickClass, $gridClass, $zoomClass=false ) {
576  $html = '';
577  ob_start();
578  $labelCount = 0;
579  $lastTick = 0;
580  unset( $lastLabel );
581  $labelCheck = isset($scale['labelCheck'])?$scale['labelCheck']:$scale['label'];
582  echo '<div id="xScale">';
583  for ( $i = 0; $i < $chart['graph']['width']; $i++ ) {
584    $x = round(100*(($i)/$chart['graph']['width']),1);
585    $timeOffset = (int)($chart['data']['x']['lo'] + ($i * $chart['data']['x']['density']));
586    if ( $scale['align'] > 1 ) {
587      $label = (int)(strftime( $labelCheck, $timeOffset )/$scale['align']);
588    } else {
589      $label = strftime( $labelCheck, $timeOffset );
590    }
591    if ( !isset($lastLabel) || ($lastLabel != $label) ) {
592      $labelCount++;
593    }
594    if ( $labelCount >= $scale['divisor'] ) {
595      $labelCount = 0;
596      if ( isset($lastLabel) ) {
597        if ( $labelClass ) {
598?>
599            <div class="<?php echo $labelClass ?>" style="left: <?php echo $x-round(100*(11/$chart['graph']['width']),1) ?>%;"><?php echo strftime( $scale['label'], $timeOffset ); ?></div>
600<?php
601        }
602        if ( $tickClass ) {
603?>
604            <div class="<?php echo $tickClass ?>" style="left: <?php echo $x ?>%;"></div>
605<?php
606        }
607        if ( $gridClass ) {
608?>
609            <div class="<?php echo $gridClass ?>" style="left: <?php echo $x ?>%;"></div>
610<?php
611        }
612        if ( $scale['name'] != 'second' && $zoomClass ) {
613          $zoomMinTime = strftime( STRF_FMT_DATETIME_DB, (int)($chart['data']['x']['lo'] + ($lastTick * $chart['data']['x']['density'])) );
614          $zoomMaxTime = strftime( STRF_FMT_DATETIME_DB, (int)($chart['data']['x']['lo'] + ($i * $chart['data']['x']['density'])) );
615?>
616            <div class="<?php echo $zoomClass ?>" style="left: <?php echo 100*($lastTick-1)/$chart['graph']['width'] ?>%; width: <?php echo round(100*($i-$lastTick)/$chart['graph']['width'],1) ?>%;" title="<?php echo translate('ZoomIn') ?>" data-on-click="tlZoomBounds" data-zoom-min-time="<?php echo $zoomMinTime ?>" data-zoom-max-time="<?php echo $zoomMaxTime ?>"></div>
617<?php
618        }
619        $lastTick = $i;
620      } # end if $lastLabel
621    }
622    $lastLabel = $label;
623  } # end foreach width segment
624
625  if ( $zoomClass ) {
626    $zoomMinTime = strftime( STRF_FMT_DATETIME_DB, (int)($chart['data']['x']['lo'] + ($lastTick * $chart['data']['x']['density'])) );
627    $zoomMaxTime = strftime( STRF_FMT_DATETIME_DB, (int)($chart['data']['x']['lo'] + ($i * $chart['data']['x']['density'])) );
628?>
629            <div class="<?php echo $zoomClass ?>" style="left: <?php echo $lastTick-1 ?>px; width: <?php echo $i-$lastTick ?>px;" title="<?php echo translate('ZoomIn') ?>" data-on-click="tlZoomBounds" data-zoom-min-time="<?php echo $zoomMinTime ?>" data-zoom-max-time="<?php echo $zoomMaxTime ?>"></div>
630<?php
631  }
632?>
633          </div>
634<?php
635  return ob_get_clean();
636} # end function drawXGrid
637
638function drawYGrid( $chart, $scale, $labelClass, $tickClass, $gridClass ) {
639  ob_start();
640?>
641  <div id="yScale">
642<?php
643  for ( $i = 0; $i < $scale['lines']; $i++ ) {
644    $label = (int)($i * $scale['divisor']);
645    $y = $chart['graph']['eventsHeight']+(int)(($i * $scale['divisor'])/$chart['data']['y']['density'])-1;
646    if ( $labelClass ) {
647?>
648       <div class="<?php echo $labelClass ?>" style="top: <?php echo $chart['graph']['height']-($y+8) ?>px;"><?php echo $label ?></div>
649<?php
650    }
651    if ( $tickClass ) {
652?>
653       <div class="<?php echo $tickClass ?>" style="top: <?php echo $chart['graph']['height']-($y+2) ?>px;"></div>
654<?php
655    }
656    if ( $gridClass ) {
657?>
658       <div class="<?php echo $gridClass ?>" style="top: <?php echo $chart['graph']['height']-($y+2) ?>px;<?php echo $i <= 0?' border-top: solid 1px black;':'' ?>"></div>
659<?php
660    }
661  } # end foreach line segment
662?>
663  </div>
664<?php
665  return ob_get_clean();
666} # end function drawYGrid
667
668$focusWindow = true;
669
670xhtmlHeaders(__FILE__, translate('Timeline'));
671?>
672<body>
673  <?php echo getNavBarHTML() ?>
674  <div id="page p-0">
675    <div class="d-flex p-1">
676      <div class="mr-auto" id="toolbar" >
677        <button id="backBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Back') ?>" disabled><i class="fa fa-arrow-left"></i></button>
678        <button id="refreshBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Refresh') ?>" ><i class="fa fa-refresh"></i></button>
679        <button id="listBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('List') ?>" ><i class="fa fa-list"></i></button>
680      </div>
681      <h2 class="align-self-end"><?php echo translate('Timeline') ?></h2>
682    </div>
683
684    <div id="content" class="chartSize">
685      <div id="topPanel" class="graphWidth">
686        <div id="imagePanel">
687          <div id="image" class="imageHeight">
688		        <img id="imageSrc" class="imageWidth" src="graphics/transparent.png" alt="<?php echo translate('ViewEvent') ?>" title="<?php echo translate('ViewEvent') ?>"/>
689          </div>
690        </div>
691        <div id="dataPanel">
692          <div id="textPanel">
693            <div id="instruction">
694              <p><?php echo translate('TimelineTip1') ?></p>
695              <p><?php echo translate('TimelineTip2') ?></p>
696              <p><?php echo translate('TimelineTip3') ?></p>
697              <p><?php echo translate('TimelineTip4') ?></p>
698              </div>
699            <div id="eventData">
700            </div>
701          </div>
702          <div id="navPanel">
703            <button type="button" title="<?php echo translate('PanLeft') ?>" data-on-click="tlPanLeft">
704            <i class="material-icons md-18">fast_rewind</i>
705            </button>
706            <button type="button" title="<?php echo translate('ZoomOut') ?>" data-on-click="tlZoomOut">
707<i class="material-icons md-18">zoom_out</i>
708            </button>
709            <button type="button" title="<?php echo translate('PanRight') ?>" data-on-click="tlPanRight">
710            <i class="material-icons md-18">fast_forward</i>
711            </button>
712          </div>
713        </div>
714      </div>
715      <div id="chartPanel">
716        <div id="chart" class="graphSize">
717<?php
718
719function drawSlot($slot,$index) {
720  global $chart;
721  global $monitors;
722  global $mouseover;
723  $height = (int)($slot['value']/$chart['data']['y']['density']);
724
725  if ( $height <= 0 )
726    return '';
727  $left = round(100*($index/$chart['graph']['width']),1);
728
729  return "<div class=\"activity monitorColour{$slot['event']['MonitorId']}\"
730            style=\"left:{$left}%; height: {$height}px;\"
731  data-event-id=\"{$slot['event']['Id']}\" data-frame-id=\"".getSlotFrame($slot)."\"".
732  ( $mouseover ? ' data-on-mouseover-this="previewEvent" data-on-click-this="showEvent"' : ' data-on-click-this="previewEvent"').
733  '></div>';
734}
735
736if ( $mode == 'overlay' ) {
737  echo drawYGrid( $chart, $majYScale, 'majLabelY', 'majTickY', 'majGridY graphWidth' );
738}
739echo drawXGrid( $chart, $majXScale, 'majLabelX', 'majTickX', 'majGridX graphHeight', 'tlzoom graphHeight' );
740if ( $mode == 'overlay' ) {
741?>
742          <div id="activity" class="activitySize">
743<?php
744    foreach ( $frameSlots as $index=>$slots ) {
745      foreach ( $slots as $slot ) {
746        echo drawSlot($slot, $index);
747      }
748    }
749?>
750          </div>
751<?php
752} else if ( $mode == 'split' ) {
753  foreach ( array_keys($monFrameSlots) as $monitorId ) {
754?>
755        <div id="activity<?php echo $monitorId ?>" class="activitySize">
756<?php
757    $currFrameSlots = &$monFrameSlots[$monitorId];
758    foreach ( $currFrameSlots as $index=>$slot ) {
759      echo drawSlot($slot, $index);
760    } # end foreach $currFrameSlots
761    unset($currFrameSlots);
762?>
763        </div>
764<?php
765  } # end foreach $MonitorId
766}
767foreach ( array_keys($monEventSlots) as $monitorId ) {
768?>
769          <div id="events<?php echo $monitorId ?>" class="events eventsSize eventsPos<?php echo $monitorId ?>">
770<?php
771  $currEventSlots = &$monEventSlots[$monitorId];
772  for ( $i = 0; $i < $chart['graph']['width']; $i++ ) {
773    if ( isset($currEventSlots[$i]) ) {
774      $slot = &$currEventSlots[$i];
775
776  $left = round(100*($i/$chart['graph']['width']),1);
777  $width = round(100*($slot['width']/$chart['graph']['width']),1);
778
779  echo "<div class=\"event monitorColour{$slot['event']['MonitorId']}\"
780            style=\"left:{$left}%; width: {$width}%;\"
781  data-event-id=\"{$slot['event']['Id']}\" data-frame-id=\"".getSlotFrame($slot)."\"".
782  ( $mouseover ? ' data-on-mouseover-this="previewEvent" data-on-click-this="showEvent"' : ' data-on-click-this="previewEvent"').
783  '></div>';
784      unset( $slot );
785    } # end if isset($currEventSlots[$i])
786  } # end foreach width segment
787  unset ($currEventSlots);
788?>
789          </div>
790<?php
791}
792?>
793        </div>
794      </div>
795      <div id="chartLabels" class="graphWidth">
796        <div id="key">
797<?php
798foreach( array_keys($monEventSlots) as $monitorId ) {
799?>
800          <span class="keyEntry"><?php echo $monitors[$monitorId]->Name() ?>
801          <div id="keyBox<?php echo $monitorId ?>" class="keyBox monitorColour<?php echo $monitorId ?>" title="<?php echo $monitors[$monitorId]->Name() ?>" style="background-color: <?php echo $monitors[$monitorId]->WebColour() ?>;"></div>
802          </span>
803<?php
804}
805?>
806        </div>
807        <div id="range"><?php echo $title ?></div>
808      </div>
809    </div>
810  </div>
811<?php xhtmlFooter() ?>
812