report_options: Syntax fix
[ninja.git] / modules / reports / libraries / Report_options.php
blob5a17380c937acc89289e9151178046e3c1935b23
1 <?php defined('SYSPATH') OR die('No direct access allowed.');
3 /**
4 * Report_options is an object representing the user-selected report
6 * It's created to improve consistency between report types and frontend/backend
7 */
8 class Report_options implements ArrayAccess, Iterator, Countable {
9 /**
10 * A name for this report type that doesn't rely on splitting class_name() by _
11 * This is used when saving the report.
13 public static $type = null;
14 /**
15 * Can contains options that must be renamed when provided - for legacy links
17 protected $rename_options;
19 /**
20 * Contains a definition of all legal keys for this type of Report_options
22 protected $properties = array(
23 'report_id' => array(
24 'type' => 'int',
25 'default' => false,
26 'description' => 'Saved report id'
28 'report_name' => array(
29 'type' => 'string',
30 'default' => false,
31 'description' => 'Name of the report'
33 'report_type' => array(
34 'type' => 'enum',
35 'default' => false,
36 'options' => array(
37 'hosts' => 'host_name',
38 'services' => 'service_description',
39 'hostgroups' => 'hostgroup',
40 'servicegroups' => 'servicegroup'
42 'description' => 'The type of objects in the report, set automatically by setting the actual objects'
44 'report_period' => array(
45 'type' => 'enum',
46 'default' => 'last7days',
47 'description' => 'A report period to generate the report over, may automatically set {start,end}_time'
49 'rpttimeperiod' => array(
50 'type' => 'enum',
51 'default' => false,
52 'description' => 'If we are to mask the alerts by a certain (nagios) timeperiod, and if so, which one'
54 'scheduleddowntimeasuptime' => array(
55 'type' => 'enum',
56 'default' => 0,
57 'description' => 'Schedule downtime as uptime: yes, no, "yes, but tell me when you cheated"'
59 'assumestatesduringnotrunning' => array(
60 'type' => 'bool',
61 'default' => true,
62 'description' => 'Whether to assume states during not running'
64 'includesoftstates' => array(
65 'type' => 'bool',
66 'default' => false, 'description' => 'Include soft states, yes/no?'
68 'objects' => array(
69 'type' => 'objsel',
70 'default' => array(),
71 'description' => 'Objects to include (note: array)'
73 'start_time' => array(
74 'type' => 'timestamp',
75 'default' => 0,
76 'description' => 'Start time for report, timestamp or date-like string'
78 'end_time' => array(
79 'type' => 'timestamp',
80 'default' => 0,
81 'description' => 'End time for report, timestamp or date-like string'
83 'sla_mode' => array(
84 'type' => 'enum',
85 'default' => 0,
86 'description' => 'Use worst, average, or best state for calculating overall health'
88 'host_filter_status' => array(
89 'type' => 'array',
90 'default' => array(),
91 'description' => 'Key: hide these. Value: map them to this instead (-2 means "secret")'
93 'service_filter_status' => array(
94 'type' => 'array',
95 'default' => array(),
96 'description' => 'Key: hide these. Value: map them to this instead (-2 means "secret")'
98 'output_format' => array(
99 'type' => 'enum',
100 'default' => 'html',
101 'options' => array(
102 'html' => 'html',
103 'csv' => 'csv',
104 'pdf' => 'pdf'
106 'generated' => true,
107 'description' => 'What type of report to generate (manually selected in web UI, generated from filename for saved reports)'
109 'skin' => array(
110 'type' => 'string',
111 'default' => '',
112 'description' => 'Use the following skin for rendering the report'
114 'use_alias' => array(
115 'type' => 'bool',
116 'default' => false,
117 'description' => "Use object's aliases instead of their names"
119 'description' => array(
120 'type' => 'string',
121 'default' => false
123 'include_alerts' => array(
124 'type' => 'bool',
125 'default' => false
130 * Placeholder used instead of fetching all objects the user is
131 * authorized to see, to be able to fetch lazily and avoid large
132 * queries or filtering large result sets.
134 const ALL_AUTHORIZED = '*';
137 * The the explicitly set options. Should not be accessed directly,
138 * outside of debugging.
140 public $options = array();
143 * This gives test suites the ability to override the
144 * "current time" as seen from the report period calculation code.
146 * This is obviously dangerous and you should never, ever use it.
148 public static $now = null;
150 private function rewrite_objects(&$name, $value, $obj) {
151 switch ($name) {
152 case 'host':
153 case 'host_name':
154 $obj->options['report_type'] = 'hosts';
155 break;
156 case 'service':
157 case 'service_description':
158 $obj->options['report_type'] = 'services';
159 break;
160 case 'hostgroup_name':
161 case 'hostgroup':
162 $obj->options['report_type'] = 'hostgroups';
163 break;
164 case 'servicegroup_name':
165 case 'servicegroup':
166 $obj->options['report_type'] = 'servicegroups';
167 break;
169 $name = 'objects';
170 return $value;
174 * Properties should be completely setup - with translations and all - before
175 * loading any options, and options are loaded by the construct, so do
176 * initialization here.
178 public function setup_properties()
180 if (isset($this->properties['report_period']))
181 $this->properties['report_period']['options'] = array(
182 "today" => _('Today'),
183 "last24hours" => _('Last 24 hours'),
184 "yesterday" => _('Yesterday'),
185 "thisweek" => _('This week'),
186 "last7days" => _('Last 7 days'),
187 "lastweek" => _('Last week'),
188 "thismonth" => _('This month'),
189 "last31days" => _('Last 31 days'),
190 "lastmonth" => _('Last month'),
191 'last3months' => _('Last 3 months'),
192 'lastquarter' => _('Last quarter'),
193 'last6months' => _('Last 6 months'),
194 'last12months' => _('Last 12 months'),
195 "thisyear" => _('This year'),
196 "lastyear" => _('Last year'),
197 'custom' => _('Custom'));
198 if (isset($this->properties['scheduleddowntimeasuptime']))
199 $this->properties['scheduleddowntimeasuptime']['options'] = array(
200 0 => _('Actual state'),
201 1 => _('Uptime'),
202 2 => _('Uptime, with difference'));
203 if (isset($this->properties['sla_mode']))
204 $this->properties['sla_mode']['options'] = array(
205 0 => _('Group availability (Worst state)'),
206 1 => _('Average'),
207 2 => _('Cluster mode (Best state)'));
208 if (isset($this->properties['rpttimeperiod']))
209 $this->properties['rpttimeperiod']['options'] = Old_Timeperiod_Model::get_all();
210 if (isset($this->properties['skin']))
211 $this->properties['skin']['default'] = config::get('config.current_skin', '*');
213 $this->rename_options = array(
214 't1' => 'start_time',
215 't2' => 'end_time',
216 'host' => array($this, 'rewrite_objects'),
217 'service' => array($this, 'rewrite_objects'),
218 'hostgroup_name' => array($this, 'rewrite_objects'),
219 'servicegroup_name' => array($this, 'rewrite_objects'),
220 'host_name' => array($this, 'rewrite_objects'),
221 'service_description' => array($this, 'rewrite_objects'),
222 'hostgroup' => array($this, 'rewrite_objects'),
223 'servicegroup' => array($this, 'rewrite_objects'),
228 * Public constructor, which optionally takes an iterable with properties to set
230 public function __construct($options=false) {
231 $this->setup_properties();
232 if ($options)
233 $this->set_options($options);
237 * Required by ArrayAccess
239 public function offsetGet($str)
241 if (!isset($this->properties[$str]))
242 return NULL;
244 return arr::search($this->options, $str, $this->properties[$str]['default']);
248 * Required by ArrayAccess
250 public function offsetSet($key, $val)
252 $this->set($key, $val);
256 * Required by ArrayAccess
258 public function offsetExists($key)
260 return isset($this->properties[$key]);
264 * Required by ArrayAccess
266 public function offsetUnset($key)
268 unset($this->options[$key]);
272 * This looks silly...
274 function properties()
276 return $this->properties;
280 * For applicable keys, this returns a list of all possible values
282 public function get_alternatives($key) {
283 if (!isset($this->properties[$key]))
284 return false;
285 if ($this->properties[$key]['type'] !== 'enum' && $this->properties[$key]['type'] !== 'array')
286 return false;
287 return $this->properties[$key]['options'];
291 * Returns the user-friendly value, given a machine-friendly option key
293 public function get_value($key) {
294 if (!isset($this->properties[$key]))
295 return false;
296 if ($this->properties[$key]['type'] !== 'enum')
297 return false;
298 if (!isset($this->properties[$key]['options'][$this[$key]]))
299 return $key;
300 return $this->properties[$key]['options'][$this[$key]];
304 * Return all objects (hosts or services) this report applies to
305 * This will return hosts or services regardless if the report object selection
306 * uses groups or not.
308 public function get_report_members() {
309 switch ($this['report_type']) {
310 case 'hosts':
311 case 'services':
312 return $this['objects'];
313 case 'hostgroups':
314 $all = HostPool_Model::all();
315 $set = HostPool_Model::none();
317 foreach ($this['objects'] as $group) {
318 $set = $set->union($all->reduce_by('groups', $group, '>='));
320 $res = array();
321 foreach ($set->it(array('name'), array()) as $arr) {
322 $res[] = $arr->get_key();
324 return $res;
325 case 'servicegroups':
326 $all = ServicePool_Model::all();
327 $set = ServicePool_Model::none();
329 foreach ($this['objects'] as $group) {
330 $set = $set->union($all->reduce_by('groups', $group, '>='));
332 $res = array();
333 foreach ($set->it(array('host.name', 'description'), array()) as $arr) {
334 $res[] = $arr->get_key();
336 return $res;
338 return false;
342 * Update the options for the report
343 * @param $options New options
345 public function set_options($options)
347 $errors = false;
348 foreach ($options as $name => $value) {
349 $errors |= intval(!$this->set($name, $value));
352 return $errors ? false : true;
357 * Calculates $this['start_time'] and $this['end_time'] based on an
358 * availability report style period such as "today", "last24hours"
359 * or "lastmonth".
361 * @param $report_period The textual period to set our options by
362 * @return false on errors, true on success
364 protected function calculate_time($report_period)
366 // self::$now should only ever be set by test suites.
367 if (self::$now) {
368 $now = self::$now;
369 } else {
370 $now = time();
372 $year_now = date('Y', $now);
373 $month_now = date('m', $now);
374 $day_now = date('d', $now);
375 $week_now = date('W', $now);
376 $weekday_now = date('w', $now)-1;
377 $time_start = false;
378 $time_end = false;
380 switch ($report_period) {
381 case 'today':
382 $time_start = mktime(0, 0, 0, $month_now, $day_now, $year_now);
383 $time_end = $now;
384 break;
385 case 'last24hours':
386 $time_start = mktime(date('H', $now), date('i', $now), date('s', $now), $month_now, $day_now -1, $year_now);
387 $time_end = $now;
388 break;
389 case 'yesterday':
390 $time_start = mktime(0, 0, 0, $month_now, $day_now -1, $year_now);
391 $time_end = mktime(0, 0, 0, $month_now, $day_now, $year_now);
392 break;
393 case 'thisweek':
394 $time_start = strtotime('today - '.$weekday_now.' days');
395 $time_end = $now;
396 break;
397 case 'last7days':
398 $time_start = strtotime('now - 7 days');
399 $time_end = $now;
400 break;
401 case 'lastweek':
402 $time_start = strtotime('midnight last monday -7 days');
403 $time_end = strtotime('midnight last monday');
404 break;
405 case 'thismonth':
406 $time_start = strtotime('midnight '.$year_now.'-'.$month_now.'-01');
407 $time_end = $now;
408 break;
409 case 'last31days':
410 $time_start = strtotime('now - 31 days');
411 $time_end = $now;
412 break;
413 case 'lastmonth':
414 $time_start = strtotime('midnight '.$year_now.'-'.$month_now.'-01 -1 month');
415 $time_end = strtotime('midnight '.$year_now.'-'.$month_now.'-01');
416 break;
417 case 'thisyear':
418 $time_start = strtotime('midnight '.$year_now.'-01-01');
419 $time_end = $now;
420 break;
421 case 'lastyear':
422 $time_start = strtotime('midnight '.$year_now.'-01-01 -1 year');
423 $time_end = strtotime('midnight '.$year_now.'-01-01');
424 break;
425 case 'last12months':
426 $time_start = strtotime('midnight '.$year_now.'-'.$month_now.'-01 -12 months');
427 $time_end = strtotime('midnight '.$year_now.'-'.$month_now.'-01');
428 break;
429 case 'last3months':
430 $time_start = strtotime('midnight '.$year_now.'-'.$month_now.'-01 -3 months');
431 $time_end = strtotime('midnight '.$year_now.'-'.$month_now.'-01');
432 break;
433 case 'last6months':
434 $time_start = strtotime('midnight '.$year_now.'-'.$month_now.'-01 -6 months');
435 $time_end = strtotime('midnight '.$year_now.'-'.$month_now.'-01');
436 break;
437 case 'lastquarter':
438 $t = getdate($now);
439 if($t['mon'] <= 3){
440 $lqstart = 'midnight '.($t['year']-1)."-10-01";
441 $lqend = 'midnight '.($t['year'])."-01-01";
442 } elseif ($t['mon'] <= 6) {
443 $lqstart = 'midnight '.$t['year']."-01-01";
444 $lqend = 'midnight '.$t['year']."-04-01";
445 } elseif ($t['mon'] <= 9){
446 $lqstart = 'midnight '.$t['year']."-04-01";
447 $lqend = 'midnight '.$t['year']."-07-01";
448 } else {
449 $lqstart = 'midnight '.$t['year']."-07-01";
450 $lqend = 'midnight '.$t['year']."-10-01";
452 $time_start = strtotime($lqstart);
453 $time_end = strtotime($lqend);
454 break;
455 case 'custom':
456 # we'll have "start_time" and "end_time" in
457 # the options when this happens
458 return true;
459 default:
460 # unknown option, ie bogosity
461 return false;
464 if($time_start > $now)
465 $time_start = $now;
467 if($time_end > $now)
468 $time_end = $now;
470 $this->options['start_time'] = $time_start;
471 $this->options['end_time'] = $time_end;
472 return true;
476 * Set an option, with some validation
478 * @param $name Option name
479 * @param $value Option value
480 * @return false on error, else true
482 public function set($name, $value)
484 if (isset($this->rename_options[$name])) {
485 if (is_callable($this->rename_options[$name])) {
486 try {
487 $value = call_user_func_array($this->rename_options[$name], array(&$name, $value, $this));
488 } catch (Exception $e) {
489 return false;
492 else if (is_string($this->rename_options[$name])) {
493 $name = $this->rename_options[$name];
497 if (!$this->validate_value($name, $value)) {
498 return false;
500 return $this->update_value($name, $value);
504 * Validates that $value isn't obviously unsuitable for $key
506 * Warning: you probably want to use set() or utilize the ArrayAccess API
508 protected function validate_value($key, &$value)
510 if (!isset($this->properties[$key])) {
511 return false;
513 switch ($this->properties[$key]['type']) {
514 case 'bool':
515 if ($value == 1 || !strcasecmp($value, "true") || !empty($value))
516 $value = true;
517 else
518 $value = false;
519 break;
520 case 'int':
521 if (!is_numeric($value) || $value != intval($value))
522 return false;
523 $value = intval($value);
524 break;
525 case 'string':
526 if (!is_string($value))
527 return false;
528 break;
529 case 'objsel':
530 if ($value == self::ALL_AUTHORIZED)
531 return true;
532 case 'array':
533 if (!is_array($value))
534 return false;
535 break;
536 case 'timestamp':
537 if (!is_numeric($value)) {
538 if (strstr($value, '-') === false)
539 return false;
540 $value = strtotime($value);
541 if ($value === false)
542 return false;
544 else {
545 $value = intval($value);
547 break;
548 case 'object':
549 if (!is_object($value)) {
550 return false;
552 break;
553 case 'enum':
554 if (!isset($this->properties[$key]['options'][$value]))
555 return false;
556 // Cast. Ohmigod…
557 $value = key(array($value => 0));
558 break;
559 default:
560 # this is an exception and should never ever happen
561 return false;
563 return true;
567 * Will actually set the provided $name to the value $value
569 * Warning: you probably want to use set() or utilize the ArrayAccess API
571 protected function update_value($name, $value)
573 switch ($name) {
574 case 'report_period':
575 if (!$this->calculate_time($value))
576 return false;
577 break;
578 case 'summary_items':
579 if ($value < 0)
580 return false;
581 break;
582 case 'host_filter_status':
583 $value = array_intersect_key($value, Reports_Model::$host_states);
584 $value = array_filter($value, function($val) {
585 return is_numeric($val);
587 break;
588 case 'service_filter_status':
589 $value = array_intersect_key($value, Reports_Model::$service_states);
590 $value = array_filter($value, function($val) {
591 return is_numeric($val);
593 break;
594 case 'start_time':
595 case 'end_time':
596 // value "impossible", or value already set by report_period
597 // (we consider anything before 1980 impossible, or at least unreasonable)
598 if ($value <= 315525600 || $value === 'undefined' || (isset($this->options[$name]) && isset($this->options['report_period']) && $this->options['report_period'] != 'custom'))
599 return false;
600 if (!is_numeric($value))
601 $value = (int)$value;
602 break;
603 default:
604 break;
606 if (!isset($this->properties[$name]))
607 return false;
608 $this->options[$name] = $value;
609 return true;
613 * Generate a standard HTTP keyval string, suitable for URLs or POST bodies.
614 * @param $anonymous If true, any option on the exact objects in this report
615 * will be purged, so it's suitable for linking to sub-reports.
616 * If false, all options will be kept, completely describing
617 * this exact report.
618 * @param $obj_only Does more-or-less the inverse of $anonymous - if true, don't
619 * include anything that does not refer to the members of the report.
621 public function as_keyval_string($anonymous=false, $obj_only=false) {
622 $opts = array();
623 foreach ($this as $key => $val) {
624 if ($obj_only && !in_array($key, array('objects', 'report_type')))
625 continue;
626 if ($anonymous && in_array($key, array('objects', 'report_type', 'report_id')))
627 continue;
628 $props = $this->properties();
629 if ($props[$key]['type'] == 'bool') {
630 $val = (int)$val;
632 $opts[$key] = $val;
634 return htmlspecialchars(http_build_query($opts));
638 * Return the report as a HTML string of hidden form elements
640 public function as_form($anonymous=false, $obj_only=false) {
641 $html_options = '';
642 $this->expand();
643 foreach ($this as $key => $val) {
644 if ($obj_only && !in_array($key, array('objects', 'report_type')))
645 continue;
646 if ($anonymous && in_array($key, array('objects', 'report_type', 'report_id')))
647 continue;
648 if (is_array($val)) {
649 foreach ($val as $k => $v)
650 $html_options .= form::hidden($key.'['.$k.']', $v);
652 else {
653 $html_options .= form::hidden($key, $val);
656 return $html_options;
660 * Return the report as a JSON string
662 public function as_json() {
663 $opts = $this->options;
664 return json_encode($opts);
668 * Expand the private structure, to make a traversal iterate over all the properties
670 public function expand() {
671 foreach ($this->properties as $key => $_) {
672 $this[$key] = $this[$key];
677 * Return the given timestamp typed property as a date string of the configured kind
679 public function get_date($var) {
680 $format = cal::get_calendar_format(true);
681 return date($format, $this[$var]);
685 * Return the given timestamp typed property as a time string
687 public function get_time($var) {
688 return date('H:i', $this[$var]);
691 /** Required by Iterator */
692 function rewind() { reset($this->options); }
693 /** Required by Iterator */
694 function current() { return current($this->options); }
695 /** Required by Iterator */
696 function key() { return key($this->options); }
697 /** Required by Iterator */
698 function next() {
699 do {
700 $x = next($this->options);
701 } while ($x !== false && isset($this->properties[key($this->options)]['generated']));
703 /** Required by Iterator */
704 function valid() { return array_key_exists(key($this->options), $this->options); }
705 /** Required by Countable */
706 function count() { return count($this->options); }
708 /** Print the options themselves when printing the object */
709 function __toString() { return var_export($this->options, true); }
712 * Finds properties to inject into.. myself
714 * You probably want setup_options_obj instead.
716 * @param $input array = false Autodiscovers options using superglobals: $input > POST > GET
717 * @return array
719 static function discover_options($input = false)
721 # not using $_REQUEST, because that includes weird, scary session vars
722 if (!empty($input)) {
723 $report_info = $input;
724 } else if (!empty($_POST)) {
725 $report_info = $_POST;
726 } else {
727 $report_info = $_GET;
730 if(isset($report_info['cal_start'], $report_info['cal_end'], $report_info['report_period']) &&
731 $report_info['cal_start'] &&
732 $report_info['cal_end'] &&
733 $report_info['report_period'] == 'custom'
736 if(!isset($report_info['time_start']) || $report_info['time_start'] === "") {
737 $report_info['time_start'] = "00:00";
739 if(!isset($report_info['time_end']) || $report_info['time_end'] === "") {
740 $report_info['time_end'] = "23:59";
743 $report_info['start_time'] = DateTime::createFromFormat(
744 nagstat::date_format(),
745 $report_info['cal_start'].' '.$report_info['time_start'].':00'
746 )->getTimestamp();
747 $report_info['end_time'] = DateTime::createFromFormat(
748 nagstat::date_format(),
749 $report_info['cal_end'].' '.$report_info['time_end'].':00'
750 )->getTimestamp();
752 unset(
753 $report_info['cal_start'],
754 $report_info['cal_end'],
755 $report_info['time_start'],
756 $report_info['time_end']
760 return $report_info;
764 * So, my issue is, basically, that op5reports needs to override how what
765 * is loaded from DB, and our saved_reports_model is useless and fragile
766 * and scary and I'll kill it, as part of fixing #7491 at the very latest.
767 * Until then, I need to expose this algorithm so op5report can access it
768 * without having to copy-paste it or access the functionality the normal
769 * way.
771 static protected function merge_with_loaded($options, $report_info)
773 if (count($report_info) > 3) {
774 foreach ($options->properties() as $key => $desc) {
775 if ($desc['type'] == 'bool' && isset($options->options[$key]))
776 $options[$key] = false;
779 $options->set_options($report_info);
780 return $options;
784 * Combines the provided properties with any saved information.
786 * You probably want setup_options_obj instead.
788 protected static function create_options_obj($report_info = false) {
789 $options = new static();
790 if (isset($report_info['report_id']) && !empty($report_info['report_id'])) {
791 if (!$options->load($report_info['report_id'])) {
792 unset($report_info['report_id']);
794 $options = static::merge_with_loaded($options, $report_info);
796 else if ($report_info) {
797 $options->set_options($report_info);
799 if (isset($options->properties['report_period']) && !isset($options->options['report_period']) && isset($options->properties['report_period']['default'])) {
800 $options->calculate_time($options['report_period']);
802 return $options;
806 * @param $type string avail|sla|summary
807 * @param $input array = false
808 * @return Report_options
810 public static function setup_options_obj($type, $input = false)
812 if (is_a($input, 'Report_options')) {
813 $class = get_class($input);
814 return new $class($input);
816 $class = ucfirst($type) . '_options';
817 if (!class_exists($class))
818 $class = 'Report_options';
820 $report_info = $class::discover_options($input);
821 $options = $class::create_options_obj($report_info);
822 return $options;
826 * Return all saved reports for this report type
828 * @returns array A id-indexed list of report names
830 public static function get_all_saved()
832 $db = Database::instance();
833 $auth = op5auth::instance();
835 $sql = "SELECT id, report_name FROM saved_reports WHERE type = ".$db->escape(static::$type);
836 if (!$auth->authorized_for('host_view_all')) {
837 $user = Auth::instance()->get_user()->username;
838 $sql .= " AND created_by = ".$db->escape($user);
841 $sql .= " ORDER BY report_name";
843 $res = $db->query($sql);
844 $return = array();
845 foreach ($res as $obj) {
846 $return[$obj->id] = $obj->report_name;
848 return $return;
852 * Loads options for a saved report by id.
853 * Primarily exists so report-type-specific
854 * load-mangling can take place.
856 protected function load_options($id)
858 $db = Database::instance();
859 $auth = op5auth::instance();
861 $opts = array();
862 $sql = "SELECT name, value FROM saved_reports_options WHERE report_id = ".(int)$id;
863 $res = $db->query($sql);
864 $props = $this->properties();
865 foreach ($res as $obj) {
866 if (!isset($props[$obj->name]))
867 continue;
868 if ($props[$obj->name]['type'] == 'array')
869 $obj->value = @unserialize($obj->value);
870 $opts[$obj->name] = $obj->value;
872 $sql = "SELECT object_name FROM saved_reports_objects WHERE report_id = ".(int)$id." ORDER BY object_name";
873 $res = $db->query($sql);
874 $opts['objects'] = array();
875 foreach ($res as $obj) {
876 $opts['objects'][] = $obj->object_name;
878 return $opts;
882 * Loads a saved report.
883 * Invoked automatically by create_options_obj
885 * @param $id int The saved report's id
886 * @returns true if any report options were loaded, false otherwise.
888 protected function load($id)
890 $db = Database::instance();
891 $auth = op5auth::instance();
893 $sql = "SELECT report_name FROM saved_reports WHERE type = " . $db->escape(static::$type) . " AND id = ".(int)$id;
894 $res = $db->query($sql);
895 if (!count($res))
896 return false;
897 $res = $res->result();
898 $this['report_name'] = $res[0]->report_name;
900 $res = $this->load_options($id);
901 $this->set_options($res);
902 $this['report_id'] = $id;
903 return true;
907 * Save a report. If it has a report_id, this does an update, otherwise,
908 * a new report is saved.
910 * @param $message If set, will contain an error message on failure.
911 * @returns boolean false if report saving failed, else true
913 public function save(&$message = NULL)
915 if (!$this['report_name']) {
916 $message = _("No report name provided");
917 return false;
919 $db = Database::instance();
920 $auth = op5auth::instance();
921 $user = Auth::instance()->get_user()->username;
922 if ($this['report_id']) {
923 $sql = "DELETE FROM saved_reports_options WHERE report_id = ".(int)$this['report_id'];
924 $db->query($sql);
925 $sql = "DELETE FROM saved_reports_objects WHERE report_id = ".(int)$this['report_id'];
926 $db->query($sql);
927 $sql = "UPDATE saved_reports SET report_name = ".$db->escape($this['report_name']).", updated_by = ".$db->escape($user).", updated_at = ".$db->escape(time());
928 $db->query($sql);
929 } else {
930 $sql = 'SELECT 1 FROM saved_reports WHERE report_name = '.$db->escape($this['report_name']).' AND type = '.$db->escape(static::$type);
931 if (count($db->query($sql)) > 0) {
932 $message = _("Another ".static::$type." report with the name {$this['report_name']} already exists");
933 return false;
936 $sql = "INSERT INTO saved_reports (type, report_name, created_by, created_at, updated_by, updated_at) VALUES (".$db->escape(static::$type).", ".$db->escape($this['report_name']).", ".$db->escape($user).", ".$db->escape(time()).", ".$db->escape($user).", ".$db->escape(time()).")";
937 $res = $db->query($sql);
938 $this['report_id'] = $res->insert_id();
941 $sql = "INSERT INTO saved_reports_options(report_id, name, value) VALUES ";
942 $this->expand();
943 $props = $this->properties();
944 $rows = array();
945 foreach ($this as $key => $val) {
946 if (in_array($key, array('objects', 'report_id', 'report_name')))
947 continue;
948 if (!isset($props[$key]))
949 continue;
950 if ($props[$key]['type'] == 'array') {
951 $val = @serialize($val);
953 # Don't save start- or end_time when we have report_period != custom
954 if (in_array($key, array('start_time', 'end_time')) && isset($this->properties['report_period']) && $this['report_period'] != 'custom') {
955 continue;
957 $rows[] = '(' . (int)$this['report_id'] . ', ' . $db->escape($key) . ', ' . $db->escape($val) . ')';
959 $sql .= implode(', ', $rows);
960 $db->query($sql);
961 if ($this['objects']) {
962 $sql = "INSERT INTO saved_reports_objects(report_id, object_name) VALUES ";
963 $rows = array();
964 foreach ($this['objects'] as $object) {
965 $rows[] = '(' . (int)$this['report_id'] . ', ' . $db->escape($object) . ')';
967 $sql .= implode(', ', $rows);
968 $db->query($sql);
970 return true;
974 * Delete a saved report.
976 * @returns boolean FAIL if deletion failed, TRUE otherwise
978 public function delete()
980 assert(is_int($this['report_id']));
981 assert($this['report_id'] >= 0);
982 $db = Database::instance();
983 $auth = op5auth::instance();
984 $sql = "DELETE FROM saved_reports_options WHERE report_id = ".(int)$this['report_id'];
985 $db->query($sql);
986 $sql = "DELETE FROM saved_reports_objects WHERE report_id = ".(int)$this['report_id'];
987 $db->query($sql);
988 $sql = "DELETE FROM saved_reports WHERE id = ".(int)$this['report_id'];
989 $db->query($sql);
990 return true;