1 <?php
defined('SYSPATH') OR die('No direct access allowed.');
4 * Report_options is an object representing the user-selected report
6 * It's created to improve consistency between report types and frontend/backend
8 class Report_options
implements ArrayAccess
, Iterator
, Countable
{
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;
15 * Can contains options that must be renamed when provided - for legacy links
17 protected $rename_options;
20 * Contains a definition of all legal keys for this type of Report_options
22 protected $properties = array(
26 'description' => 'Saved report id'
28 'report_name' => array(
31 'description' => 'Name of the report'
33 'report_type' => 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(
46 'default' => 'last7days',
47 'description' => 'A report period to generate the report over, may automatically set {start,end}_time'
49 'rpttimeperiod' => array(
52 'description' => 'If we are to mask the alerts by a certain (nagios) timeperiod, and if so, which one'
54 'scheduleddowntimeasuptime' => array(
57 'description' => 'Schedule downtime as uptime: yes, no, "yes, but tell me when you cheated"'
59 'assumestatesduringnotrunning' => array(
62 'description' => 'Whether to assume states during not running'
64 'includesoftstates' => array(
66 'default' => false, 'description' => 'Include soft states, yes/no?'
71 'description' => 'Objects to include (note: array)'
73 'start_time' => array(
74 'type' => 'timestamp',
76 'description' => 'Start time for report, timestamp or date-like string'
79 'type' => 'timestamp',
81 'description' => 'End time for report, timestamp or date-like string'
86 'description' => 'Use worst, average, or best state for calculating overall health'
88 'host_filter_status' => array(
91 'description' => 'Key: hide these. Value: map them to this instead (-2 means "secret")'
93 'service_filter_status' => array(
96 'description' => 'Key: hide these. Value: map them to this instead (-2 means "secret")'
98 'output_format' => array(
107 'description' => 'What type of report to generate (manually selected in web UI, generated from filename for saved reports)'
112 'description' => 'Use the following skin for rendering the report'
114 'use_alias' => array(
117 'description' => "Use object's aliases instead of their names"
119 'description' => array(
123 'include_alerts' => array(
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;
151 * Properties should be completely setup - with translations and all - before
152 * loading any options, and options are loaded by the construct, so do
153 * initialization here.
155 public function setup_properties()
157 if (isset($this->properties
['report_period']))
158 $this->properties
['report_period']['options'] = array(
159 "today" => _('Today'),
160 "last24hours" => _('Last 24 hours'),
161 "yesterday" => _('Yesterday'),
162 "thisweek" => _('This week'),
163 "last7days" => _('Last 7 days'),
164 "lastweek" => _('Last week'),
165 "thismonth" => _('This month'),
166 "last31days" => _('Last 31 days'),
167 "lastmonth" => _('Last month'),
168 'last3months' => _('Last 3 months'),
169 'lastquarter' => _('Last quarter'),
170 'last6months' => _('Last 6 months'),
171 'last12months' => _('Last 12 months'),
172 "thisyear" => _('This year'),
173 "lastyear" => _('Last year'),
174 'custom' => _('Custom'));
175 if (isset($this->properties
['scheduleddowntimeasuptime']))
176 $this->properties
['scheduleddowntimeasuptime']['options'] = array(
177 0 => _('Actual state'),
179 2 => _('Uptime, with difference'));
180 if (isset($this->properties
['sla_mode']))
181 $this->properties
['sla_mode']['options'] = array(
182 0 => _('Group availability (Worst state)'),
184 2 => _('Cluster mode (Best state)'));
185 if (isset($this->properties
['rpttimeperiod']))
186 $this->properties
['rpttimeperiod']['options'] = Old_Timeperiod_Model
::get_all();
187 if (isset($this->properties
['skin']))
188 $this->properties
['skin']['default'] = config
::get('config.current_skin', '*');
190 $this->rename_options
= array(
191 't1' => 'start_time',
194 'service' => 'objects',
195 'hostgroup_name' => 'objects',
196 'servicegroup_name' => 'objects',
197 'host_name' => 'objects',
198 'service_description' => 'objects',
199 'hostgroup' => 'objects',
200 'servicegroup' => 'objects',
205 * Public constructor, which optionally takes an iterable with properties to set
207 public function __construct($options=false) {
208 $this->setup_properties();
210 $this->set_options($options);
214 * Required by ArrayAccess
216 public function offsetGet($str)
218 if (!isset($this->properties
[$str]))
221 return arr
::search($this->options
, $str, $this->properties
[$str]['default']);
225 * Required by ArrayAccess
227 public function offsetSet($key, $val)
229 $this->set($key, $val);
233 * Required by ArrayAccess
235 public function offsetExists($key)
237 return isset($this->properties
[$key]);
241 * Required by ArrayAccess
243 public function offsetUnset($key)
245 unset($this->options
[$key]);
249 * This looks silly...
251 function properties()
253 return $this->properties
;
257 * For applicable keys, this returns a list of all possible values
259 public function get_alternatives($key) {
260 if (!isset($this->properties
[$key]))
262 if ($this->properties
[$key]['type'] !== 'enum' && $this->properties
[$key]['type'] !== 'array')
264 return $this->properties
[$key]['options'];
268 * Returns the user-friendly value, given a machine-friendly option key
270 public function get_value($key) {
271 if (!isset($this->properties
[$key]))
273 if ($this->properties
[$key]['type'] !== 'enum')
275 if (!isset($this->properties
[$key]['options'][$this[$key]]))
277 return $this->properties
[$key]['options'][$this[$key]];
281 * Return all objects (hosts or services) this report applies to
282 * This will return hosts or services regardless if the report object selection
283 * uses groups or not.
285 public function get_report_members() {
286 switch ($this['report_type']) {
289 return $this['objects'];
291 $all = HostPool_Model
::all();
292 $set = HostPool_Model
::none();
294 foreach ($this['objects'] as $group) {
295 $set = $set->union($all->reduce_by('groups', $group, '>='));
298 foreach ($set->it(array('name'), array()) as $arr) {
299 $res[] = $arr->get_key();
302 case 'servicegroups':
303 $all = ServicePool_Model
::all();
304 $set = ServicePool_Model
::none();
306 foreach ($this['objects'] as $group) {
307 $set = $set->union($all->reduce_by('groups', $group, '>='));
310 foreach ($set->it(array('host.name', 'description'), array()) as $arr) {
311 $res[] = $arr->get_key();
319 * Update the options for the report
320 * @param $options New options
322 public function set_options($options)
325 foreach ($options as $name => $value) {
326 $errors |
= intval(!$this->set($name, $value));
329 return $errors ?
false : true;
334 * Calculates $this['start_time'] and $this['end_time'] based on an
335 * availability report style period such as "today", "last24hours"
338 * @param $report_period The textual period to set our options by
339 * @return false on errors, true on success
341 protected function calculate_time($report_period)
343 // self::$now should only ever be set by test suites.
349 $year_now = date('Y', $now);
350 $month_now = date('m', $now);
351 $day_now = date('d', $now);
352 $week_now = date('W', $now);
353 $weekday_now = date('w', $now)-1;
357 switch ($report_period) {
359 $time_start = mktime(0, 0, 0, $month_now, $day_now, $year_now);
363 $time_start = mktime(date('H', $now), date('i', $now), date('s', $now), $month_now, $day_now -1, $year_now);
367 $time_start = mktime(0, 0, 0, $month_now, $day_now -1, $year_now);
368 $time_end = mktime(0, 0, 0, $month_now, $day_now, $year_now);
371 $time_start = strtotime('today - '.$weekday_now.' days');
375 $time_start = strtotime('now - 7 days');
379 $time_start = strtotime('midnight last monday -7 days');
380 $time_end = strtotime('midnight last monday');
383 $time_start = strtotime('midnight '.$year_now.'-'.$month_now.'-01');
387 $time_start = strtotime('now - 31 days');
391 $time_start = strtotime('midnight '.$year_now.'-'.$month_now.'-01 -1 month');
392 $time_end = strtotime('midnight '.$year_now.'-'.$month_now.'-01');
395 $time_start = strtotime('midnight '.$year_now.'-01-01');
399 $time_start = strtotime('midnight '.$year_now.'-01-01 -1 year');
400 $time_end = strtotime('midnight '.$year_now.'-01-01');
403 $time_start = strtotime('midnight '.$year_now.'-'.$month_now.'-01 -12 months');
404 $time_end = strtotime('midnight '.$year_now.'-'.$month_now.'-01');
407 $time_start = strtotime('midnight '.$year_now.'-'.$month_now.'-01 -3 months');
408 $time_end = strtotime('midnight '.$year_now.'-'.$month_now.'-01');
411 $time_start = strtotime('midnight '.$year_now.'-'.$month_now.'-01 -6 months');
412 $time_end = strtotime('midnight '.$year_now.'-'.$month_now.'-01');
417 $lqstart = 'midnight '.($t['year']-1)."-10-01";
418 $lqend = 'midnight '.($t['year'])."-01-01";
419 } elseif ($t['mon'] <= 6) {
420 $lqstart = 'midnight '.$t['year']."-01-01";
421 $lqend = 'midnight '.$t['year']."-04-01";
422 } elseif ($t['mon'] <= 9){
423 $lqstart = 'midnight '.$t['year']."-04-01";
424 $lqend = 'midnight '.$t['year']."-07-01";
426 $lqstart = 'midnight '.$t['year']."-07-01";
427 $lqend = 'midnight '.$t['year']."-10-01";
429 $time_start = strtotime($lqstart);
430 $time_end = strtotime($lqend);
433 # we'll have "start_time" and "end_time" in
434 # the options when this happens
437 # unknown option, ie bogosity
441 if($time_start > $now)
447 $this->options
['start_time'] = $time_start;
448 $this->options
['end_time'] = $time_end;
453 * Set an option, with some validation
455 * @param $name Option name
456 * @param $value Option value
457 * @return false on error, else true
459 public function set($name, $value)
461 if (isset($this->rename_options
[$name])) {
462 if (is_callable($this->rename_options
[$name])) {
464 $value = call_user_func($this->rename_options
[$name], $name, $value, $this);
465 } catch (Exception
$e) {
469 else if (is_string($this->rename_options
[$name])) {
470 $name = $this->rename_options
[$name];
474 if (!$this->validate_value($name, $value)) {
477 return $this->update_value($name, $value);
481 * Validates that $value isn't obviously unsuitable for $key
483 * Warning: you probably want to use set() or utilize the ArrayAccess API
485 protected function validate_value($key, &$value)
487 if (!isset($this->properties
[$key])) {
490 switch ($this->properties
[$key]['type']) {
492 if ($value == 1 ||
!strcasecmp($value, "true") ||
!empty($value))
498 if (!is_numeric($value) ||
$value != intval($value))
500 $value = intval($value);
503 if (!is_string($value))
507 if ($value == self
::ALL_AUTHORIZED
)
510 if (!is_array($value))
514 if (!is_numeric($value)) {
515 if (strstr($value, '-') === false)
517 $value = strtotime($value);
518 if ($value === false)
522 $value = intval($value);
526 if (!is_object($value)) {
531 if (!isset($this->properties
[$key]['options'][$value]))
534 $value = key(array($value => 0));
537 # this is an exception and should never ever happen
544 * Will actually set the provided $name to the value $value
546 * Warning: you probably want to use set() or utilize the ArrayAccess API
548 protected function update_value($name, $value)
551 case 'report_period':
552 if (!$this->calculate_time($value))
555 case 'summary_items':
559 case 'host_filter_status':
560 $value = array_intersect_key($value, Reports_Model
::$host_states);
561 $value = array_filter($value, function($val) {
562 return is_numeric($val);
565 case 'service_filter_status':
566 $value = array_intersect_key($value, Reports_Model
::$service_states);
567 $value = array_filter($value, function($val) {
568 return is_numeric($val);
573 // value "impossible", or value already set by report_period
574 // (we consider anything before 1980 impossible, or at least unreasonable)
575 if ($value <= 315525600 ||
$value === 'undefined' ||
(isset($this->options
[$name]) && isset($this->options
['report_period']) && $this->options
['report_period'] != 'custom'))
577 if (!is_numeric($value))
578 $value = (int)$value;
583 if (!isset($this->properties
[$name]))
585 $this->options
[$name] = $value;
590 * Generate a standard HTTP keyval string, suitable for URLs or POST bodies.
591 * @param $anonymous If true, any option on the exact objects in this report
592 * will be purged, so it's suitable for linking to sub-reports.
593 * If false, all options will be kept, completely describing
595 * @param $obj_only Does more-or-less the inverse of $anonymous - if true, don't
596 * include anything that does not refer to the members of the report.
598 public function as_keyval_string($anonymous=false, $obj_only=false) {
600 foreach ($this as $key => $val) {
601 if ($obj_only && !in_array($key, array('objects', 'report_type')))
603 if ($anonymous && in_array($key, array('objects', 'report_type', 'report_id')))
605 $props = $this->properties();
606 if ($props[$key]['type'] == 'bool') {
611 return htmlspecialchars(http_build_query($opts));
615 * Return the report as a HTML string of hidden form elements
617 public function as_form($anonymous=false, $obj_only=false) {
620 foreach ($this as $key => $val) {
621 if ($obj_only && !in_array($key, array('objects', 'report_type')))
623 if ($anonymous && in_array($key, array('objects', 'report_type', 'report_id')))
625 if (is_array($val)) {
626 foreach ($val as $k => $v)
627 $html_options .= form
::hidden($key.'['.$k.']', $v);
630 $html_options .= form
::hidden($key, $val);
633 return $html_options;
637 * Return the report as a JSON string
639 public function as_json() {
640 $opts = $this->options
;
641 return json_encode($opts);
645 * Expand the private structure, to make a traversal iterate over all the properties
647 public function expand() {
648 foreach ($this->properties
as $key => $_) {
649 $this[$key] = $this[$key];
654 * Return the given timestamp typed property as a date string of the configured kind
656 public function get_date($var) {
657 $format = cal
::get_calendar_format(true);
658 return date($format, $this[$var]);
662 * Return the given timestamp typed property as a time string
664 public function get_time($var) {
665 return date('H:i', $this[$var]);
668 /** Required by Iterator */
669 function rewind() { reset($this->options
); }
670 /** Required by Iterator */
671 function current() { return current($this->options
); }
672 /** Required by Iterator */
673 function key() { return key($this->options
); }
674 /** Required by Iterator */
677 $x = next($this->options
);
678 } while ($x !== false && isset($this->properties
[key($this->options
)]['generated']));
680 /** Required by Iterator */
681 function valid() { return array_key_exists(key($this->options
), $this->options
); }
682 /** Required by Countable */
683 function count() { return count($this->options
); }
685 /** Print the options themselves when printing the object */
686 function __toString() { return var_export($this->options
, true); }
689 * Finds properties to inject into.. myself
691 * You probably want setup_options_obj instead.
693 * @param $input array = false Autodiscovers options using superglobals: $input > POST > GET
696 static function discover_options($input = false)
698 # not using $_REQUEST, because that includes weird, scary session vars
699 if (!empty($input)) {
700 $report_info = $input;
701 } else if (!empty($_POST)) {
702 $report_info = $_POST;
704 $report_info = $_GET;
707 if(isset($report_info['cal_start'], $report_info['cal_end'], $report_info['report_period']) &&
708 $report_info['cal_start'] &&
709 $report_info['cal_end'] &&
710 $report_info['report_period'] == 'custom'
713 if(!isset($report_info['time_start']) ||
$report_info['time_start'] === "") {
714 $report_info['time_start'] = "00:00";
716 if(!isset($report_info['time_end']) ||
$report_info['time_end'] === "") {
717 $report_info['time_end'] = "23:59";
720 $report_info['start_time'] = DateTime
::createFromFormat(
721 nagstat
::date_format(),
722 $report_info['cal_start'].' '.$report_info['time_start'].':00'
724 $report_info['end_time'] = DateTime
::createFromFormat(
725 nagstat
::date_format(),
726 $report_info['cal_end'].' '.$report_info['time_end'].':00'
730 $report_info['cal_start'],
731 $report_info['cal_end'],
732 $report_info['time_start'],
733 $report_info['time_end']
741 * So, my issue is, basically, that op5reports needs to override how what
742 * is loaded from DB, and our saved_reports_model is useless and fragile
743 * and scary and I'll kill it, as part of fixing #7491 at the very latest.
744 * Until then, I need to expose this algorithm so op5report can access it
745 * without having to copy-paste it or access the functionality the normal
748 static protected function merge_with_loaded($options, $report_info)
750 if (count($report_info) > 3) {
751 foreach ($options->properties() as $key => $desc) {
752 if ($desc['type'] == 'bool' && isset($options->options
[$key]))
753 $options[$key] = false;
756 $options->set_options($report_info);
761 * Combines the provided properties with any saved information.
763 * You probably want setup_options_obj instead.
765 protected static function create_options_obj($report_info = false) {
766 $options = new static();
767 if (isset($report_info['report_id']) && !empty($report_info['report_id'])) {
768 if (!$options->load($report_info['report_id'])) {
769 unset($report_info['report_id']);
771 $options = static::merge_with_loaded($options, $report_info);
773 else if ($report_info) {
774 $options->set_options($report_info);
776 if (isset($options->properties
['report_period']) && !isset($options->options
['report_period']) && isset($options->properties
['report_period']['default'])) {
777 $options->calculate_time($options['report_period']);
783 * @param $type string avail|sla|summary
784 * @param $input array = false
785 * @return Report_options
787 public static function setup_options_obj($type, $input = false)
789 if (is_a($input, 'Report_options')) {
790 $class = get_class($input);
791 return new $class($input);
793 $class = ucfirst($type) . '_options';
794 if (!class_exists($class))
795 $class = 'Report_options';
797 $report_info = $class::discover_options($input);
798 $options = $class::create_options_obj($report_info);
803 * Return all saved reports for this report type
805 * @returns array A id-indexed list of report names
807 public static function get_all_saved()
809 $db = Database
::instance();
810 $auth = op5auth
::instance();
812 $sql = "SELECT id, report_name FROM saved_reports WHERE type = ".$db->escape(static::$type);
813 if (!$auth->authorized_for('host_view_all')) {
814 $user = Auth
::instance()->get_user()->username
;
815 $sql .= " AND created_by = ".$db->escape($user);
818 $sql .= " ORDER BY report_name";
820 $res = $db->query($sql);
822 foreach ($res as $obj) {
823 $return[$obj->id
] = $obj->report_name
;
829 * Loads options for a saved report by id.
830 * Primarily exists so report-type-specific
831 * load-mangling can take place.
833 protected function load_options($id)
835 $db = Database
::instance();
836 $auth = op5auth
::instance();
839 $sql = "SELECT name, value FROM saved_reports_options WHERE report_id = ".(int)$id;
840 $res = $db->query($sql);
841 $props = $this->properties();
842 foreach ($res as $obj) {
843 if (!isset($props[$obj->name
]))
845 if ($props[$obj->name
]['type'] == 'array')
846 $obj->value
= @unserialize
($obj->value
);
847 $opts[$obj->name
] = $obj->value
;
849 $sql = "SELECT object_name FROM saved_reports_objects WHERE report_id = ".(int)$id;
850 $res = $db->query($sql);
851 $opts['objects'] = array();
852 foreach ($res as $obj) {
853 $opts['objects'][] = $obj->object_name
;
859 * Loads a saved report.
860 * Invoked automatically by create_options_obj
862 * @param $id int The saved report's id
863 * @returns true if any report options were loaded, false otherwise.
865 protected function load($id)
867 $db = Database
::instance();
868 $auth = op5auth
::instance();
870 $sql = "SELECT report_name FROM saved_reports WHERE type = " . $db->escape(static::$type) . " AND id = ".(int)$id;
871 $res = $db->query($sql);
874 $res = $res->result();
875 $this['report_name'] = $res[0]->report_name
;
877 $res = $this->load_options($id);
878 $this->set_options($res);
879 $this['report_id'] = $id;
884 * Save a report. If it has a report_id, this does an update, otherwise,
885 * a new report is saved.
887 * @param $message If set, will contain an error message on failure.
888 * @returns boolean false if report saving failed, else true
890 public function save(&$message = NULL)
892 if (!$this['report_name']) {
893 $message = _("No report name provided");
896 if (!$this['objects']) {
897 $message = _("Can't save report without report members");
900 $db = Database
::instance();
901 $auth = op5auth
::instance();
902 $user = Auth
::instance()->get_user()->username
;
903 if ($this['report_id']) {
904 $sql = "DELETE FROM saved_reports_options WHERE report_id = ".(int)$this['report_id'];
906 $sql = "DELETE FROM saved_reports_objects WHERE report_id = ".(int)$this['report_id'];
908 $sql = "UPDATE saved_reports SET report_name = ".$db->escape($this['report_name']).", updated_by = ".$db->escape($user).", updated_at = ".$db->escape(time());
911 $sql = 'SELECT 1 FROM saved_reports WHERE report_name = '.$db->escape($this['report_name']).' AND type = '.$db->escape(static::$type);
912 if (count($db->query($sql)) > 0) {
913 $message = _("Another ".static::$type." report with the name {$this['report_name']} already exists");
917 $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()).")";
918 $res = $db->query($sql);
919 $this['report_id'] = $res->insert_id();
922 $sql = "INSERT INTO saved_reports_options(report_id, name, value) VALUES ";
924 $props = $this->properties();
926 foreach ($this as $key => $val) {
927 if (in_array($key, array('objects', 'report_id', 'report_name')))
929 if (!isset($props[$key]))
931 if ($props[$key]['type'] == 'array') {
932 $val = @serialize
($val);
934 # Don't save start- or end_time when we have report_period != custom
935 if (in_array($key, array('start_time', 'end_time')) && $this['report_period'] != 'custom') {
938 $rows[] = '(' . (int)$this['report_id'] . ', ' . $db->escape($key) . ', ' . $db->escape($val) . ')';
940 $sql .= implode(', ', $rows);
942 $sql = "INSERT INTO saved_reports_objects(report_id, object_name) VALUES ";
944 foreach ($this['objects'] as $object) {
945 $rows[] = '(' . (int)$this['report_id'] . ', ' . $db->escape($object) . ')';
947 $sql .= implode(', ', $rows);
953 * Delete a saved report.
955 * @returns boolean FAIL if deletion failed, TRUE otherwise
957 public function delete()
959 assert(is_int($this['report_id']));
960 assert($this['report_id'] >= 0);
961 $db = Database
::instance();
962 $auth = op5auth
::instance();
963 $sql = "DELETE FROM saved_reports_options WHERE report_id = ".(int)$this['report_id'];
965 $sql = "DELETE FROM saved_reports_objects WHERE report_id = ".(int)$this['report_id'];
967 $sql = "DELETE FROM saved_reports WHERE id = ".(int)$this['report_id'];